]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
merge from trunk r14444
authorChristos Tsantilas <chtsanti@users.sourceforge.net>
Mon, 14 Dec 2015 19:09:24 +0000 (21:09 +0200)
committerChristos Tsantilas <chtsanti@users.sourceforge.net>
Mon, 14 Dec 2015 19:09:24 +0000 (21:09 +0200)
12 files changed:
1  2 
src/Downloader.cc
src/Downloader.h
src/Makefile.am
src/client_side.cc
src/client_side.h
src/client_side_request.cc
src/servers/Server.cc
src/ssl/PeerConnector.cc
src/ssl/PeerConnector.h
src/ssl/support.cc
src/ssl/support.h
src/tunnel.cc

index 5ab4fe5abf84378a08a8df6296b7880febf6fd1f,0000000000000000000000000000000000000000..34bdfc7522f1d0e95d0aa82b32209131a59b73ab
mode 100644,000000..100644
--- /dev/null
@@@ -1,221 -1,0 +1,222 @@@
-             if (context != context->http->getConn()->getCurrentContext().getRaw())
 +#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"
 +
 +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)
 +{
 +}
 +
 +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()) {
 +        context->registerWithConn();
 +        processParsedRequest(context);
 +
 +        /**/
 +        if (context->flags.deferred) {
-     Must(getConcurrentRequestCount() == 1);
++            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 *
 +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);
 +    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)
 +{
 +    Must(context != NULL);
- Downloader::writeControlMsgAndCall(ClientSocketContext *context, HttpReply *rep, AsyncCall::Pointer &call)
++    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
-     bool exceedSize = (getCurrentContext()->startOfOutput() && existingContent > -1 && (size_t)existingContent > MaxObjectSize) || 
++Downloader::writeControlMsgAndCall(HttpReply *rep, AsyncCall::Pointer &call)
 +{
 +}
 +
 +void
 +Downloader::handleReply(HttpReply *reply, StoreIOBuffer receivedData)
 +{
++    ClientSocketContext::Pointer context = pipeline.front();
 +    bool existingContent = reply ? reply->content_length : 0;
-         getCurrentContext()->http->out.size += receivedData.length;
-         getCurrentContext()->noteSentBodyBytes(receivedData.length);
++    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);
-     switch (getCurrentContext()->socketState()) {
++        context->http->out.size += receivedData.length;
++        context->noteSentBodyBytes(receivedData.length);
 +    }
 +
-         getCurrentContext()->pullData();
++    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;
 +}
index f17580989ede3e2050945569591146c568d50f0a,0000000000000000000000000000000000000000..152d2d8e0b0e79b70bf2363e7581db0be7322c14
mode 100644,000000..100644
--- /dev/null
@@@ -1,69 -1,0 +1,69 @@@
-     virtual void writeControlMsgAndCall(ClientSocketContext *context, HttpReply *rep, AsyncCall::Pointer &call);
 +#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 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/Makefile.am
Simple merge
index e21c2abb024c2d7497074f60d0a5bc20a1a04745,c377c0c98cbef70f38585613cd4f93067a3d5bcd..0e4612df09d732d4fde0272c7ab89d0217c116e3
@@@ -810,10 -673,8 +673,9 @@@ ConnStateData::swanSong(
      debugs(33, 2, HERE << clientConnection);
      flags.readMore = false;
      DeregisterRunner(this);
 -    clientdbEstablished(clientConnection->remote, -1);  /* decrement */
 +    if (clientConnection != NULL)
 +        clientdbEstablished(clientConnection->remote, -1);  /* decrement */
-     assert(areAllContextsForThisConnection());
-     freeAllContexts();
+     pipeline.terminateAll(0);
  
      unpinConnection(true);
  
@@@ -3417,12 -3135,7 +3145,9 @@@ ConnStateData::ConnStateData(const Mast
      pinning.peer = NULL;
  
      // store the details required for creating more MasterXaction objects as new requests come in
-     clientConnection = xact->tcpClient;
-     port = xact->squidPort;
-     if (port != NULL)
-         transferProtocol = port->transport; // default to the *_port protocol= setting. may change later.
 -    log_addr = xact->tcpClient->remote;
 +    if (xact->tcpClient != NULL)
 +        log_addr = xact->tcpClient->remote;
++
      log_addr.applyMask(Config.Addrs.client_netmask);
  
      // register to receive notice of Squid signal events
index 78e4fae6ef6b3e813612f9dc365d3b98145f6fbf,5cfd05da97769cf86cef9359b6bed41e00db93da..d264c20599aef53998ea030cf851ff97451a4557
@@@ -182,35 -201,13 +201,13 @@@ public
      /// Traffic parsing
      bool clientParseRequests();
      void readNextRequest();
-     ClientSocketContext::Pointer getCurrentContext() const;
-     void addContextToQueue(ClientSocketContext * context);
-     int getConcurrentRequestCount() const;
-     virtual bool isOpen() const;
-     /// Update flags and timeout after the first byte received
-     void receivedFirstByte();
  
-     // HttpControlMsgSink API
-     virtual void sendControlMsg(HttpControlMsg msg);
+     /// try to make progress on a transaction or read more I/O
+     void kick();
  
-     // Client TCP connection details from comm layer.
-     Comm::ConnectionPointer clientConnection;
-     /**
-      * The transfer protocol currently being spoken on this connection.
-      * HTTP/1 CONNECT and HTTP/2 SETTINGS offers the ability to change
-      * protocols on the fly.
-      */
-     AnyP::ProtocolVersion transferProtocol;
-     struct In {
-         In();
-         ~In();
-         bool maybeMakeSpaceAvailable();
 -    bool isOpen() const;
++    virtual bool isOpen() const;
  
-         Http1::TeChunkedParser *bodyParser; ///< parses chunked request body
-         SBuf buf;
-     } in;
+     Http1::TeChunkedParser *bodyParser; ///< parses HTTP/1.1 chunked request body
  
      /** number of body bytes we need to comm_read for the "current" request
       *
          AsyncCall::Pointer closeHandler; /*The close handler for pinned server side connection*/
      } pinning;
  
-     /// Squid listening port details where this connection arrived.
-     AnyP::PortCfgPointer port;
 +    /// If the port is not set then it is a connection-less object 
 +    /// created by an internal squid subsystem
 +    bool connectionless() const { return port == NULL; }
++
      bool transparent() const;
-     bool reading() const;
-     void stopReading(); ///< cancels comm_read if it is scheduled
  
      /// true if we stopped receiving the request
      const char *stoppedReceiving() const { return stoppedReceiving_; }
Simple merge
index 0000000000000000000000000000000000000000,e60911f8d977001e2197ea8377f63b1d1edd0605..9065f92fa4fcc7f0f712d57a7686c027417151f3
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,206 +1,208 @@@
 -    transferProtocol(xact->squidPort->transport),
+ /*
+  * Copyright (C) 1996-2015 The Squid Software Foundation and contributors
+  *
+  * Squid software is distributed under GPLv2+ license and includes
+  * contributions from numerous individuals and organizations.
+  * Please see the COPYING and CONTRIBUTORS files for details.
+  */
+ #include "squid.h"
+ #include "anyp/PortCfg.h"
+ #include "client_side.h"
+ #include "comm.h"
+ #include "comm/Read.h"
+ #include "Debug.h"
+ #include "fd.h"
+ #include "fde.h"
+ #include "MasterXaction.h"
+ #include "servers/Server.h"
+ #include "SquidConfig.h"
+ #include "StatCounters.h"
+ #include "tools.h"
+ Server::Server(const MasterXaction::Pointer &xact) :
+     AsyncJob("::Server"), // kids overwrite
+     clientConnection(xact->tcpClient),
 -{}
+     port(xact->squidPort),
+     receivedFirstByte_(false)
++{
++    if (xact->squidPort != NULL)
++        transferProtocol = xact->squidPort->transport;
++}
+ bool
+ Server::doneAll() const
+ {
+     // servers are not done while the connection is open
+     return !Comm::IsConnOpen(clientConnection) &&
+            BodyProducer::doneAll();
+ }
+ void
+ Server::start()
+ {
+     // TODO: shuffle activity from ConnStateData
+ }
+ void
+ Server::swanSong()
+ {
+     if (Comm::IsConnOpen(clientConnection))
+         clientConnection->close();
+     BodyProducer::swanSong();
+ }
+ void
+ Server::stopReading()
+ {
+     if (reading()) {
+         Comm::ReadCancel(clientConnection->fd, reader);
+         reader = NULL;
+     }
+ }
+ bool
+ Server::maybeMakeSpaceAvailable()
+ {
+     if (inBuf.spaceSize() < 2) {
+         const SBuf::size_type haveCapacity = inBuf.length() + inBuf.spaceSize();
+         if (haveCapacity >= Config.maxRequestBufferSize) {
+             debugs(33, 4, "request buffer full: client_request_buffer_max_size=" << Config.maxRequestBufferSize);
+             return false;
+         }
+         if (haveCapacity == 0) {
+             // haveCapacity is based on the SBuf visible window of the MemBlob buffer, which may fill up.
+             // at which point bump the buffer back to default. This allocates a new MemBlob with any un-parsed bytes.
+             inBuf.reserveCapacity(CLIENT_REQ_BUF_SZ);
+         } else {
+             const SBuf::size_type wantCapacity = min(static_cast<SBuf::size_type>(Config.maxRequestBufferSize), haveCapacity*2);
+             inBuf.reserveCapacity(wantCapacity);
+         }
+         debugs(33, 2, "growing request buffer: available=" << inBuf.spaceSize() << " used=" << inBuf.length());
+     }
+     return (inBuf.spaceSize() >= 2);
+ }
+ void
+ Server::readSomeData()
+ {
+     if (reading())
+         return;
+     debugs(33, 4, clientConnection << ": reading request...");
+     // we can only read if there is more than 1 byte of space free
+     if (Config.maxRequestBufferSize - inBuf.length() < 2)
+         return;
+     typedef CommCbMemFunT<Server, CommIoCbParams> Dialer;
+     reader = JobCallback(33, 5, Dialer, this, Server::doClientRead);
+     Comm::Read(clientConnection, reader);
+ }
+ void
+ Server::doClientRead(const CommIoCbParams &io)
+ {
+     debugs(33,5, io.conn);
+     Must(reading());
+     reader = NULL;
+     /* Bail out quickly on Comm::ERR_CLOSING - close handlers will tidy up */
+     if (io.flag == Comm::ERR_CLOSING) {
+         debugs(33,5, io.conn << " closing Bailout.");
+         return;
+     }
+     assert(Comm::IsConnOpen(clientConnection));
+     assert(io.conn->fd == clientConnection->fd);
+     /*
+      * Don't reset the timeout value here. The value should be
+      * counting Config.Timeout.request and applies to the request
+      * as a whole, not individual read() calls.
+      * Plus, it breaks our lame *HalfClosed() detection
+      */
+     maybeMakeSpaceAvailable();
+     CommIoCbParams rd(this); // will be expanded with ReadNow results
+     rd.conn = io.conn;
+     switch (Comm::ReadNow(rd, inBuf)) {
+     case Comm::INPROGRESS:
+         if (inBuf.isEmpty())
+             debugs(33, 2, io.conn << ": no data to process, " << xstrerr(rd.xerrno));
+         readSomeData();
+         return;
+     case Comm::OK:
+         statCounter.client_http.kbytes_in += rd.size;
+         if (!receivedFirstByte_)
+             receivedFirstByte();
+         // may comm_close or setReplyToError
+         if (!handleReadData())
+             return;
+         /* Continue to process previously read data */
+         break;
+     case Comm::ENDFILE: // close detected by 0-byte read
+         debugs(33, 5, io.conn << " closed?");
+         if (connFinishedWithConn(rd.size)) {
+             clientConnection->close();
+             return;
+         }
+         /* It might be half-closed, we can't tell */
+         fd_table[io.conn->fd].flags.socket_eof = true;
+         commMarkHalfClosed(io.conn->fd);
+         fd_note(io.conn->fd, "half-closed");
+         /* There is one more close check at the end, to detect aborted
+          * (partial) requests. At this point we can't tell if the request
+          * is partial.
+          */
+         /* Continue to process previously read data */
+         break;
+     // case Comm::COMM_ERROR:
+     default: // no other flags should ever occur
+         debugs(33, 2, io.conn << ": got flag " << rd.flag << "; " << xstrerr(rd.xerrno));
+         pipeline.terminateAll(rd.xerrno);
+         io.conn->close();
+         return;
+     }
+     afterClientRead();
+ }
+ /** callback handling the Comm::Write completion
+  *
+  * Will call afterClientWrite(size_t) to sync the I/O state.
+  * Then writeSomeData() to initiate any followup writes that
+  * could be immediately done.
+  */
+ void
+ Server::clientWriteDone(const CommIoCbParams &io)
+ {
+     debugs(33,5, io.conn);
+     Must(writer != nullptr);
+     writer = nullptr;
+     /* Bail out quickly on Comm::ERR_CLOSING - close handlers will tidy up */
+     if (io.flag == Comm::ERR_CLOSING || !Comm::IsConnOpen(clientConnection)) {
+         debugs(33,5, io.conn << " closing Bailout.");
+         return;
+     }
+     Must(io.conn->fd == clientConnection->fd);
+     if (io.flag && pipeline.front())
+         pipeline.front()->initiateClose("write failure");
+     afterClientWrite(io.size); // update state
+     writeSomeData(); // maybe schedules another write
+ }
index 880b9f236e45ae0d16513da02448beb2210c3b0e,f2b3ecf44fd45055480ade6eeab893739fe50cc4..c3c7410455b860bb66df7cb97fa9b30cb50ff8a4
@@@ -612,105 -585,7 +608,105 @@@ Ssl::PeerConnector::status() cons
      return buf.content();
  }
  
- SSL_CTX *
 +/// 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, "OK! certificate downloaded, status: " << downloadStatus << " data size: " << obj.length());
 +
 +    // Get ServerBio from SSL object
 +    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);
 +
 +    // Parse Certificate. Assume that it is in DER format. Probably we should handle PEM or other formats too
 +    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 donwload and 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;
 +    SSL *ssl = fd_table[fd].ssl;
 +    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;
 +}
 +
+ Security::ContextPtr
  Ssl::BlindPeerConnector::getSslContext()
  {
      if (const CachePeer *peer = serverConnection()->getPeer()) {
index f00980927adde65dadf1f20391ed03940ff205ca,5d5548b3509b8bdd87afa84c8bc3ac3c4624ccac..4ecbaffe4ea2c5b88357256e002ce7a4194e6948
@@@ -177,17 -167,8 +179,14 @@@ private
      /// Check SSL errors returned from cert validator against sslproxy_cert_error access list
      Ssl::CertErrors *sslCrtvdCheckForErrors(Ssl::CertValidationResponse const &, Ssl::ErrorDetail *&);
  
-     /// Callback function called when squid receive message from cert validator helper
-     static void sslCrtvdHandleReplyWrapper(void *data, Ssl::CertValidationResponse const &);
      /// 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
index 5e314e059948c44d936d211af966652f64568219,429fd812068208c496bbcf15da79970e92e51007..edcadbcbdcdcde785869538446f8e16e5a841f28
  
  #include <cerrno>
  
- static void setSessionCallbacks(SSL_CTX *ctx);
 +// 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";
  
@@@ -474,29 -471,8 +474,9 @@@ Ssl::Initialize(void
      ssl_ex_index_ssl_errors =  SSL_get_ex_new_index(0, (void *) "ssl_errors", NULL, NULL, &ssl_free_SslErrors);
      ssl_ex_index_ssl_cert_chain = SSL_get_ex_new_index(0, (void *) "ssl_cert_chain", NULL, NULL, &ssl_free_CertChain);
      ssl_ex_index_ssl_validation_counter = SSL_get_ex_new_index(0, (void *) "ssl_validation_counter", NULL, NULL, &ssl_free_int);
 +    ssl_ex_index_ssl_untrusted_chain = SSL_get_ex_new_index(0, (void *) "ssl_untrusted_chain", NULL, NULL, &ssl_free_CertChain);
  }
  
- bool
- Ssl::loadCerts(const char *certsFile, Ssl::CertsIndexedList &list)
- {
-     BIO *in = BIO_new_file(certsFile, "r");
-     if (!in) {
-         debugs(83, DBG_IMPORTANT, "Failed to open '" << certsFile << "' to load certificates");
-         return false;
-     }
-     
-     X509 *aCert;
-     while((aCert = PEM_read_bio_X509(in, NULL, NULL, NULL))) {
-         static char buffer[2048];
-         X509_NAME_oneline(X509_get_subject_name(aCert), buffer, sizeof(buffer));
-         list.insert(std::pair<SBuf, X509 *>(SBuf(buffer), aCert));
-     }
-     debugs(83, 4, "Loaded " << list.size() << " certificates from file: '" << certsFile << "'");
-     BIO_free(in);
-     return true;
- }
  #if defined(SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS)
  static void
  ssl_info_cb(const SSL *ssl, int where, int ret)
@@@ -1144,35 -1097,29 +1101,55 @@@ void Ssl::addChainToSslContext(Security
      }
  }
  
 +static const char *
 +hasAuthorityInfoAccessCaIssuers(X509 *cert)
 +{
 +    AUTHORITY_INFO_ACCESS *info;
 +    if (!cert)
 +        return NULL;
 +    info = (AUTHORITY_INFO_ACCESS *)X509_get_ext_d2i(cert, NID_info_access, NULL, NULL);
 +    if (!info)
 +        return NULL;
 +
 +    static char uri[MAX_URL];
 +    uri[0] = '\0';
 +
 +    for (int i = 0; i < sk_ACCESS_DESCRIPTION_num(info); i++) {
 +        ACCESS_DESCRIPTION *ad = sk_ACCESS_DESCRIPTION_value(info, i);
 +        if (OBJ_obj2nid(ad->method) == NID_ad_ca_issuers) {
 +            if (ad->location->type == GEN_URI) {
 +                xstrncpy(uri, (char *)ASN1_STRING_data(ad->location->d.uniformResourceIdentifier), sizeof(uri));
 +            }
 +            break;
 +        }
 +    }
 +    AUTHORITY_INFO_ACCESS_free(info);
 +    return uri[0] != '\0' ? uri : NULL;
 +}
 +
+ bool
+ Ssl::loadCerts(const char *certsFile, Ssl::CertsIndexedList &list)
+ {
+     BIO *in = BIO_new_file(certsFile, "r");
+     if (!in) {
+         debugs(83, DBG_IMPORTANT, "Failed to open '" << certsFile << "' to load certificates");
+         return false;
+     }
+     X509 *aCert;
+     while((aCert = PEM_read_bio_X509(in, NULL, NULL, NULL))) {
+         static char buffer[2048];
+         X509_NAME_oneline(X509_get_subject_name(aCert), buffer, sizeof(buffer));
+         list.insert(std::pair<SBuf, X509 *>(SBuf(buffer), aCert));
+     }
+     debugs(83, 4, "Loaded " << list.size() << " certificates from file: '" << certsFile << "'");
+     BIO_free(in);
+     return true;
+ }
  /// quickly find a certificate with a given issuer in Ssl::CertsIndexedList.
  static X509 *
 -findCertByIssuerFast(X509_STORE_CTX *ctx, Ssl::CertsIndexedList &list, X509 *cert)
 +findCertByIssuerFast(Ssl::CertsIndexedList &list, X509 *cert)
  {
      static char buffer[2048];
  
index 67772492020a26f843b1b85798c1334ebe426977,86b4931dede7605edd9769fbdd0ebbf86e772e67..9146c36bd092fb075844118499d3cab632812a9b
  #if HAVE_OPENSSL_ENGINE_H
  #include <openssl/engine.h>
  #endif
 +#include <queue>
  #include <map>
  
  /**
   \defgroup ServerProtocolSSLAPI Server-Side SSL API
   \ingroup ServerProtocol
@@@ -257,15 -242,15 +289,22 @@@ bool configureSSLUsingPkeyAndCertFromMe
    \ingroup ServerProtocolSSLAPI
    * Adds the certificates in certList to the certificate chain of the SSL context
   */
- void addChainToSslContext(SSL_CTX *sslContext, STACK_OF(X509) *certList);
+ void addChainToSslContext(Security::ContextPtr sslContext, STACK_OF(X509) *certList);
+ /**
+   \ingroup ServerProtocolSSLAPI
+   * Configures sslContext to use squid untrusted certificates internal list
+   * to complete certificate chains when verifies SSL servers certificates.
+  */
+ void useSquidUntrusted(SSL_CTX *sslContext);
  
 +/**
 +  \ingroup ServerProtocolSSLAPI
 +  * Configures sslContext to use squid untrusted certificates internal list
 +  * to complete certificate chains when verifies SSL servers certificates.
 + */
 +void useSquidUntrusted(SSL_CTX *sslContext);
 +
  /**
   \ingroup ServerProtocolSSLAPI
   *  Read certificate, private key and any certificates which must be chained from files.
diff --cc src/tunnel.cc
Simple merge