]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
merge from trunk
authorChristos Tsantilas <chtsanti@users.sourceforge.net>
Fri, 2 May 2014 08:34:46 +0000 (11:34 +0300)
committerChristos Tsantilas <chtsanti@users.sourceforge.net>
Fri, 2 May 2014 08:34:46 +0000 (11:34 +0300)
14 files changed:
1  2 
configure.ac
src/cache_cf.cc
src/cf.data.pre
src/client_side.cc
src/client_side.h
src/client_side_request.h
src/ssl/Makefile.am
src/ssl/PeerConnector.cc
src/ssl/PeerConnector.h
src/ssl/ServerBump.cc
src/ssl/ServerBump.h
src/ssl/bio.cc
src/ssl/support.cc
src/ssl/support.h

diff --cc configure.ac
index 5a0afb5b37a3569611c773fbbf7b605f5cda2924,46b76499afccbc89912b115ed2e7d664355cd109..22b6f9edbb4298da6a34737bcdbb0506bb0aec67
@@@ -2366,16 -2251,7 +2251,13 @@@ AC_CHECK_HEADERS( 
    netinet/in.h \
    netinet/in_systm.h \
    netinet/ip_fil_compat.h \
-   netinet/tcp.h \
-   openssl/engine.h \
-   openssl/txt_db.h \
-   ostream \
+   netinet/tcp.h \
 +  openssl/bio.h \
 +  openssl/err.h \
 +  openssl/md5.h \
 +  openssl/opensslv.h \
 +  openssl/ssl.h \
 +  openssl/x509v3.h \
    paths.h \
    poll.h \
    pwd.h \
diff --cc src/cache_cf.cc
index a7420ffb6cdcfef5714d9e4611d33b34265cbf8e,3c21bacbc80bc727769193db19e51dfc8a53db0b..6ca7b61248f511715856ec51816715466eb78171
@@@ -4552,20 -4660,16 +4660,19 @@@ static void parse_sslproxy_ssl_bump(acl
          sslBumpCfgRr::lastDeprecatedRule = Ssl::bumpEnd;
      }
  
-     acl_access *A = new acl_access;
-     A->allow = allow_t(ACCESS_ALLOWED);
+     allow_t action = allow_t(ACCESS_ALLOWED);
  
      if (strcmp(bm, Ssl::BumpModeStr[Ssl::bumpClientFirst]) == 0) {
-         A->allow.kind = Ssl::bumpClientFirst;
+         action.kind = Ssl::bumpClientFirst;
          bumpCfgStyleNow = bcsNew;
      } else if (strcmp(bm, Ssl::BumpModeStr[Ssl::bumpServerFirst]) == 0) {
-         A->allow.kind = Ssl::bumpServerFirst;
+         action.kind = Ssl::bumpServerFirst;
          bumpCfgStyleNow = bcsNew;
-         A->allow.kind = Ssl::bumpPeekAndSplice;
 +    } else if (strcmp(bm, Ssl::BumpModeStr[Ssl::bumpPeekAndSplice]) == 0) {
++        action.kind = Ssl::bumpPeekAndSplice;
 +        bumpCfgStyleNow = bcsNew;
      } else if (strcmp(bm, Ssl::BumpModeStr[Ssl::bumpNone]) == 0) {
-         A->allow.kind = Ssl::bumpNone;
+         action.kind = Ssl::bumpNone;
          bumpCfgStyleNow = bcsNew;
      } else if (strcmp(bm, "allow") == 0) {
          debugs(3, DBG_CRITICAL, "SECURITY NOTICE: auto-converting deprecated "
diff --cc src/cf.data.pre
Simple merge
index 70379490ca9de98db095c5a6969b4bcefb52b854,b42dc70ed522f2a540a96f6f1e476409585ffac5..4831e42b6780f4fa52b3b290c8615c481654e5e4
  #if USE_DELAY_POOLS
  #include "ClientInfo.h"
  #endif
- #if USE_SSL
+ #if USE_OPENSSL
 +#include "ssl/bio.h"
- #include "ssl/ProxyCerts.h"
  #include "ssl/context_storage.h"
+ #include "ssl/gadgets.h"
  #include "ssl/helper.h"
+ #include "ssl/ProxyCerts.h"
  #include "ssl/ServerBump.h"
  #include "ssl/support.h"
- #include "ssl/gadgets.h"
  #endif
  #if USE_SSL_CRTD
- #include "ssl/crtd_message.h"
  #include "ssl/certificate_db.h"
+ #include "ssl/crtd_message.h"
  #endif
  
- #if HAVE_LIMITS_H
- #include <limits.h>
- #endif
- #if HAVE_MATH_H
- #include <math.h>
- #endif
- #if HAVE_LIMITS
+ #include <climits>
+ #include <cmath>
  #include <limits>
- #endif
  
  #if LINGERING_CLOSE
  #define comm_close comm_lingering_close
@@@ -3833,27 -3832,25 +3841,28 @@@ ConnStateData::getSslContextStart(
          Ssl::CertificateProperties certProperties;
          buildSslCertGenerationParams(certProperties);
          sslBumpCertKey = certProperties.dbKey().c_str();
-         assert(sslBumpCertKey.defined() && sslBumpCertKey[0] != '\0');
+         assert(sslBumpCertKey.size() > 0 && sslBumpCertKey[0] != '\0');
  
 -        debugs(33, 5, HERE << "Finding SSL certificate for " << sslBumpCertKey << " in cache");
 -        Ssl::LocalContextStorage *ssl_ctx_cache = Ssl::TheGlobalContextStorage.getLocalStorage(port->s);
 -        SSL_CTX * dynCtx = NULL;
 -        Ssl::SSL_CTX_Pointer *cachedCtx = ssl_ctx_cache ? ssl_ctx_cache->get(sslBumpCertKey.termedBuf()) : NULL;
 -        if (cachedCtx && (dynCtx = cachedCtx->get())) {
 -            debugs(33, 5, HERE << "SSL certificate for " << sslBumpCertKey << " have found in cache");
 -            if (Ssl::verifySslCertificate(dynCtx, certProperties)) {
 -                debugs(33, 5, HERE << "Cached SSL certificate for " << sslBumpCertKey << " is valid");
 -                getSslContextDone(dynCtx);
 -                return;
 +        // Disable caching for bumpPeekAndSplice mode
 +        if (!(sslServerBump && sslServerBump->mode == Ssl::bumpPeekAndSplice)) {
 +            debugs(33, 5, HERE << "Finding SSL certificate for " << sslBumpCertKey << " in cache");
-             Ssl::LocalContextStorage & ssl_ctx_cache(Ssl::TheGlobalContextStorage.getLocalStorage(port->s));
++            Ssl::LocalContextStorage * ssl_ctx_cache = Ssl::TheGlobalContextStorage.getLocalStorage(port->s);
 +            SSL_CTX * dynCtx = NULL;
-             Ssl::SSL_CTX_Pointer *cachedCtx = ssl_ctx_cache.get(sslBumpCertKey.termedBuf());
++            Ssl::SSL_CTX_Pointer *cachedCtx = ssl_ctx_cache ? ssl_ctx_cache->get(sslBumpCertKey.termedBuf()) : NULL;
 +            if (cachedCtx && (dynCtx = cachedCtx->get())) {
 +                debugs(33, 5, HERE << "SSL certificate for " << sslBumpCertKey << " have found in cache");
 +                if (Ssl::verifySslCertificate(dynCtx, certProperties)) {
 +                    debugs(33, 5, HERE << "Cached SSL certificate for " << sslBumpCertKey << " is valid");
 +                    getSslContextDone(dynCtx);
 +                    return;
 +                } else {
 +                    debugs(33, 5, HERE << "Cached SSL certificate for " << sslBumpCertKey << " is out of date. Delete this certificate from cache");
-                     ssl_ctx_cache.del(sslBumpCertKey.termedBuf());
++                    if (ssl_ctx_cache)
++                        ssl_ctx_cache->del(sslBumpCertKey.termedBuf());
 +                }
              } else {
 -                debugs(33, 5, HERE << "Cached SSL certificate for " << sslBumpCertKey << " is out of date. Delete this certificate from cache");
 -                if (ssl_ctx_cache)
 -                    ssl_ctx_cache->del(sslBumpCertKey.termedBuf());
 +                debugs(33, 5, HERE << "SSL certificate for " << sslBumpCertKey << " haven't found in cache");
              }
 -        } else {
 -            debugs(33, 5, HERE << "SSL certificate for " << sslBumpCertKey << " haven't found in cache");
          }
  
  #if USE_SSL_CRTD
@@@ -3954,87 -3954,14 +3973,87 @@@ ConnStateData::switchToHttps(HttpReques
          sslServerBump = new Ssl::ServerBump(request);
  
          // will call httpsPeeked() with certificate and connection, eventually
-         FwdState::fwdStart(clientConnection, sslServerBump->entry, sslServerBump->request);
+         FwdState::fwdStart(clientConnection, sslServerBump->entry, sslServerBump->request.getRaw());
          return;
      }
 +    else if (bumpServerMode == Ssl::bumpPeekAndSplice) {
 +        request->flags.sslPeek = true;
 +        sslServerBump = new Ssl::ServerBump(request, NULL, Ssl::bumpPeekAndSplice);
 +        startPeekAndSplice();
 +        return;        
 +    }
  
      // otherwise, use sslConnectHostOrIp
      getSslContextStart();
  }
  
-     FwdState::fwdStart(clientConnection, sslServerBump->entry, sslServerBump->request);
 +/** negotiate an SSL connection */
 +static void
 +clientPeekAndSpliceSSL(int fd, void *data)
 +{
 +    ConnStateData *conn = (ConnStateData *)data;
 +    SSL *ssl = fd_table[fd].ssl;
 +
 +    debugs(83, 2, "Start peek and splice on" << fd);
 +
 +    if (!Squid_SSL_accept(conn, clientPeekAndSpliceSSL))
 +        debugs(83, 2, "SSL_accept failed.");
 +
 +    BIO *b = SSL_get_rbio(ssl);
 +    assert(b);
 +    Ssl::ClientBio *bio = static_cast<Ssl::ClientBio *>(b->ptr);
 +    if (bio->gotHello()) {
 +        debugs(83, 2, "I got hello. Start forwarding the request!!! ");
 +        Comm::SetSelect(fd, COMM_SELECT_READ, NULL, NULL, 0);
 +        Comm::SetSelect(fd, COMM_SELECT_WRITE, NULL, NULL, 0);
 +        conn->startPeekAndSpliceDone();
 +        return;
 +    }
 +}
 +
 +void ConnStateData::startPeekAndSplice()
 +{
 +    // will call httpsPeeked() with certificate and connection, eventually
 +    SSL_CTX *unConfiguredCTX = Ssl::createSSLContext(port->signingCert, port->signPkey, *port);
 +    fd_table[clientConnection->fd].dynamicSslContext = unConfiguredCTX;
 +
 +    if (!httpsCreate(clientConnection, unConfiguredCTX))
 +        return;
 +
 +    // commSetConnTimeout() was called for this request before we switched.
 +
 +    // Disable the client read handler until CachePeer selection is complete
 +    Comm::SetSelect(clientConnection->fd, COMM_SELECT_READ, NULL, NULL, 0);
 +    Comm::SetSelect(clientConnection->fd, COMM_SELECT_READ, clientPeekAndSpliceSSL, this, 0);
 +    switchedToHttps_ = true;
 +
 +    SSL *ssl = fd_table[clientConnection->fd].ssl;
 +    BIO *b = SSL_get_rbio(ssl);
 +    Ssl::ClientBio *bio = static_cast<Ssl::ClientBio *>(b->ptr);
 +    bio->hold(true);
 +}
 +
 +void
 +ConnStateData::startPeekAndSpliceDone()
 +{
++    FwdState::fwdStart(clientConnection, sslServerBump->entry, sslServerBump->request.getRaw());
 +}
 +
 +void
 +ConnStateData::doPeekAndSpliceStep()
 +{
 +    SSL *ssl = fd_table[clientConnection->fd].ssl;
 +    BIO *b = SSL_get_rbio(ssl);
 +    assert(b);
 +    Ssl::ClientBio *bio = static_cast<Ssl::ClientBio *>(b->ptr);
 +
 +    debugs(33, 5, HERE << "PeekAndSplice mode, proceed with client negotiation. Currrent state:" << SSL_state_string_long(ssl));
 +    bio->hold(false);
 +
 +    Comm::SetSelect(clientConnection->fd, COMM_SELECT_WRITE, clientNegotiateSSL, this, 0);
 +    switchedToHttps_ = true;
 +}
 +
  void
  ConnStateData::httpsPeeked(Comm::ConnectionPointer serverConnection)
  {
index c5a0099234cc1cdd0e989bd9e9ff2ce3eadf6fd7,7ec69f182b567b62d766c7ba7cdb1176bd8a291e..a7511d82dd1c4ce03bef8dd5b0a7e003b55af9b4
@@@ -331,15 -331,10 +331,18 @@@ public
      /// the client-side-detected error response instead of getting stuck.
      void quitAfterError(HttpRequest *request); // meant to be private
  
- #if USE_SSL
+     /// The caller assumes responsibility for connection closure detection.
+     void stopPinnedConnectionMonitoring();
+ #if USE_OPENSSL
 +    /// Initializes and starts a peek-and-splice negotiation with the SSL client
 +    void startPeekAndSplice();
 +    /// Called when the initialization of peek-and-splice negotiation finidhed
 +    void startPeekAndSpliceDone();
 +    /// Called when a peek-and-splice step finished. For example after
 +    /// server-side SSL certificates received and client-side SSL certificates
 +    /// generated
 +    void doPeekAndSpliceStep();
      /// called by FwdState when it is done bumping the server
      void httpsPeeked(Comm::ConnectionPointer serverConnection);
  
Simple merge
Simple merge
index 0000000000000000000000000000000000000000,d6c32347a260efdfbe4b99d4725b7e7953fea1f4..0682abdce0e8f8f9268ae82860f917ebc6e2568d
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,545 +1,586 @@@
 -    SSL *ssl;
+ /*
+  * DEBUG: section 17    Request Forwarding
+  *
+  */
+ #include "squid.h"
+ #include "acl/FilledChecklist.h"
+ #include "base/AsyncCbdataCalls.h"
+ #include "CachePeer.h"
+ #include "client_side.h"
+ #include "comm/Loops.h"
+ #include "errorpage.h"
+ #include "fde.h"
+ #include "globals.h"
+ #include "HttpRequest.h"
+ #include "neighbors.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"
+ #include "SquidConfig.h"
+ CBDATA_NAMESPACED_CLASS_INIT(Ssl, PeerConnector);
+ Ssl::PeerConnector::PeerConnector(
+     HttpRequestPointer &aRequest,
+     const Comm::ConnectionPointer &aServerConn,
+     AsyncCall::Pointer &aCallback):
+     AsyncJob("Ssl::PeerConnector"),
+     request(aRequest),
+     serverConn(aServerConn),
+     callback(aCallback)
+ {
+     // if this throws, the caller's cb dialer is not our CbDialer
+     Must(dynamic_cast<CbDialer*>(callback->getDialer()));
+ }
+ Ssl::PeerConnector::~PeerConnector()
+ {
+     debugs(83, 5, "Peer connector " << this << " gone");
+ }
+ bool Ssl::PeerConnector::doneAll() const
+ {
+     return (!callback || callback->canceled()) && AsyncJob::doneAll();
+ }
+ /// Preps connection and SSL state. Calls negotiate().
+ void
+ Ssl::PeerConnector::start()
+ {
+     AsyncJob::start();
+     if (prepareSocket()) {
+         initializeSsl();
+         negotiateSsl();
+     }
+ }
+ void
+ Ssl::PeerConnector::commCloseHandler(const CommCloseCbParams &params)
+ {
+     debugs(83, 5, "FD " << params.fd << ", Ssl::PeerConnector=" << params.data);
+     connectionClosed("Ssl::PeerConnector::commCloseHandler");
+ }
+ void
+ Ssl::PeerConnector::connectionClosed(const char *reason)
+ {
+     mustStop(reason);
+     callback = NULL;
+ }
+ bool
+ Ssl::PeerConnector::prepareSocket()
+ {
+     const int fd = serverConnection()->fd;
+     if (!Comm::IsConnOpen(serverConn) || fd_table[serverConn->fd].closing()) {
+         connectionClosed("Ssl::PeerConnector::prepareSocket");
+         return false;
+     }
+     // watch for external connection closures
+     typedef CommCbMemFunT<Ssl::PeerConnector, CommCloseCbParams> Dialer;
+     closeHandler = JobCallback(9, 5, Dialer, this, Ssl::PeerConnector::commCloseHandler);
+     comm_add_close_handler(fd, closeHandler);
+     return true;
+ }
+ void
+ Ssl::PeerConnector::initializeSsl()
+ {
 -    if ((ssl = SSL_new(sslContext)) == NULL) {
+     SSL_CTX *sslContext = NULL;
+     const CachePeer *peer = serverConnection()->getPeer();
+     const int fd = serverConnection()->fd;
+     if (peer) {
+         assert(peer->use_ssl);
+         sslContext = peer->sslContext;
+     } else {
+         sslContext = ::Config.ssl_client.sslContext;
+     }
+     assert(sslContext);
 -    SSL_set_fd(ssl, fd);
 -
++    SSL *ssl = Ssl::CreateClient(sslContext, fd, "server https start");
++    if (!ssl) {
+         ErrorState *anErr = new ErrorState(ERR_SOCKET_FAILURE, Http::scInternalServerError, request.getRaw());
+         anErr->xerrno = errno;
+         debugs(83, DBG_IMPORTANT, "Error allocating SSL handle: " << ERR_error_string(ERR_get_error(), NULL));
+         bail(anErr);
+         return;
+     }
 -
 -    fd_table[fd].ssl = ssl;
 -    fd_table[fd].read_method = &ssl_read_method;
 -    fd_table[fd].write_method = &ssl_write_method;
+     if (peer) {
+         if (peer->ssldomain)
+             SSL_set_ex_data(ssl, ssl_ex_index_server, peer->ssldomain);
+ #if NOT_YET
+         else if (peer->name)
+             SSL_set_ex_data(ssl, ssl_ex_index_server, peer->name);
+ #endif
+         else
+             SSL_set_ex_data(ssl, ssl_ex_index_server, peer->host);
+         if (peer->sslSession)
+             SSL_set_session(ssl, peer->sslSession);
++    } else if (request->clientConnectionManager->sslBumpMode == Ssl::bumpPeekAndSplice) {
++        SSL *clientSsl = fd_table[request->clientConnectionManager->clientConnection->fd].ssl;
++        BIO *b = SSL_get_rbio(clientSsl);
++        Ssl::ClientBio *clnBio = static_cast<Ssl::ClientBio *>(b->ptr);
++        const Ssl::Bio::sslFeatures &features = clnBio->getFeatures();
++        if (features.sslVersion != -1) {
++            SSL_set_ssl_method(ssl, Ssl::method(features.toSquidSSLVersion()));
++            if (!features.serverName.empty())
++                SSL_set_tlsext_host_name(ssl, features.serverName.c_str());
++            if (!features.clientRequestedCiphers.empty())
++                SSL_set_cipher_list(ssl, features.clientRequestedCiphers.c_str());
++#ifdef SSL_OP_NO_COMPRESSION /* XXX: OpenSSL 0.9.8k lacks SSL_OP_NO_COMPRESSION */
++            if (features.compressMethod == 0)
++                SSL_set_options(ssl, SSL_OP_NO_COMPRESSION);
++#endif
++            if (features.sslVersion >= 3) {
++                b = SSL_get_rbio(ssl);
++                Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(b->ptr);
++                srvBio->setClientRandom(features.client_random);
++                srvBio->recordInput(true);
++            }
++        }
+     } else {
+         // 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 char *hostname = request->GetHost();
+         const bool hostnameIsIp = request->GetHostIsNumeric();
+         const bool isConnectRequest = request->clientConnectionManager.valid() &&
+                                       !request->clientConnectionManager->port->flags.isIntercepted();
+         if (!request->flags.sslPeek || isConnectRequest)
+             SSL_set_ex_data(ssl, ssl_ex_index_server, (void*)hostname);
+         // Use SNI TLS extension only when we connect directly
+         // to the origin server and we know the server host name.
+         if (!hostnameIsIp)
+             Ssl::setClientSNI(ssl, hostname);
+     }
+     // If CertValidation Helper used do not lookup checklist for errors,
+     // but keep a list of errors to send it to CertValidator
+     if (!Ssl::TheConfig.ssl_crt_validator) {
+         // Create the ACL check list now, while we have access to more info.
+         // The list is used in ssl_verify_cb() and is freed in ssl_free().
+         if (acl_access *acl = ::Config.ssl_client.cert_error) {
+             ACLFilledChecklist *check = new ACLFilledChecklist(acl, request.getRaw(), dash_str);
+             // check->fd(fd); XXX: need client FD here
+             SSL_set_ex_data(ssl, ssl_ex_index_cert_error_check, check);
+         }
+     }
+     // store peeked cert to check SQUID_X509_V_ERR_CERT_CHANGE
+     X509 *peeked_cert;
+     if (request->clientConnectionManager.valid() &&
+             request->clientConnectionManager->serverBump() &&
+             (peeked_cert = request->clientConnectionManager->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);
+     }
+ }
+ void
+ Ssl::PeerConnector::negotiateSsl()
+ {
+     if (!Comm::IsConnOpen(serverConnection()) || fd_table[serverConnection()->fd].closing())
+         return;
+     const int fd = serverConnection()->fd;
+     SSL *ssl = fd_table[fd].ssl;
+     const int result = SSL_connect(ssl);
+     if (result <= 0) {
+         handleNegotiateError(result);
+         return; // we might be gone by now
+     }
+     if (request->clientConnectionManager.valid()) {
+         // remember the server certificate from the ErrorDetail object
+         if (Ssl::ServerBump *serverBump = request->clientConnectionManager->serverBump()) {
+             serverBump->serverCert.reset(SSL_get_peer_certificate(ssl));
+             // remember validation errors, if any
+             if (Ssl::CertErrors *errs = static_cast<Ssl::CertErrors *>(SSL_get_ex_data(ssl, ssl_ex_index_ssl_errors)))
+                 serverBump->sslErrors = cbdataReference(errs);
+         }
+     }
+     if (serverConnection()->getPeer() && !SSL_session_reused(ssl)) {
+         if (serverConnection()->getPeer()->sslSession)
+             SSL_SESSION_free(serverConnection()->getPeer()->sslSession);
+         serverConnection()->getPeer()->sslSession = SSL_get1_session(ssl);
+     }
+     if (Ssl::TheConfig.ssl_crt_validator) {
+         Ssl::CertValidationRequest validationRequest;
+         // WARNING: Currently we do not use any locking for any of the
+         // members of the Ssl::CertValidationRequest class. In this code the
+         // Ssl::CertValidationRequest object used only to pass data to
+         // Ssl::CertValidationHelper::submit method.
+         validationRequest.ssl = ssl;
+         validationRequest.domainName = request->GetHost();
+         if (Ssl::CertErrors *errs = static_cast<Ssl::CertErrors *>(SSL_get_ex_data(ssl, ssl_ex_index_ssl_errors)))
+             // validationRequest disappears on return so no need to cbdataReference
+             validationRequest.errors = errs;
+         else
+             validationRequest.errors = NULL;
+         try {
+             debugs(83, 5, "Sending SSL certificate for validation to ssl_crtvd.");
+             Ssl::CertValidationHelper::GetInstance()->sslSubmit(validationRequest, sslCrtvdHandleReplyWrapper, this);
+             return;
+         } catch (const std::exception &e) {
+             debugs(83, DBG_IMPORTANT, "ERROR: Failed to compose ssl_crtvd " <<
+                    "request for " << validationRequest.domainName <<
+                    " certificate: " << e.what() << "; will now block to " <<
+                    "validate that certificate.");
+             // fall through to do blocking in-process generation.
+             ErrorState *anErr = new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request.getRaw());
+             bail(anErr);
+             if (serverConnection()->getPeer()) {
+                 peerConnectFailed(serverConnection()->getPeer());
+             }
+             serverConn->close();
+             return;
+         }
+     }
+     callBack();
+ }
++void
++Ssl::PeerConnector::checkForPeekAndSplice()
++{
++    SSL *ssl = fd_table[serverConn->fd].ssl;
++    BIO *b = SSL_get_rbio(ssl);
++    Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(b->ptr);
++    debugs(83,5, "Will check for peek and splice on fd " << serverConn->fd);
++    const bool splice = false;
++    if (!splice) {
++        //Allow write, proceed with the connection
++        srvBio->holdWrite(false);
++        srvBio->recordInput(false);
++        Comm::SetSelect(serverConn->fd, COMM_SELECT_WRITE, &NegotiateSsl, this, 0);
++        debugs(83,5, "Retry the fwdNegotiateSSL on fd " << serverConn->fd);
++    }
++}
++
+ void
+ Ssl::PeerConnector::sslCrtvdHandleReplyWrapper(void *data, Ssl::CertValidationResponse const &validationResponse)
+ {
+     Ssl::PeerConnector *connector = (Ssl::PeerConnector *)(data);
+     connector->sslCrtvdHandleReply(validationResponse);
+ }
+ void
+ Ssl::PeerConnector::sslCrtvdHandleReply(Ssl::CertValidationResponse const &validationResponse)
+ {
+     Ssl::CertErrors *errs = NULL;
+     Ssl::ErrorDetail *errDetails = NULL;
+     bool validatorFailed = false;
+     if (!Comm::IsConnOpen(serverConnection())) {
+         return;
+     }
+     debugs(83,5, request->GetHost() << " cert validation result: " << validationResponse.resultCode);
+     if (validationResponse.resultCode == HelperReply::Error)
+         errs = sslCrtvdCheckForErrors(validationResponse, errDetails);
+     else if (validationResponse.resultCode != HelperReply::Okay)
+         validatorFailed = true;
+     if (!errDetails && !validatorFailed) {
+         callBack();
+         return;
+     }
+     ErrorState *anErr = NULL;
+     if (validatorFailed) {
+         anErr = new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request.getRaw());
+     }  else {
+         // Check the list error with
+         if (errDetails && request->clientConnectionManager.valid()) {
+             // remember the server certificate from the ErrorDetail object
+             if (Ssl::ServerBump *serverBump = request->clientConnectionManager->serverBump()) {
+                 // remember validation errors, if any
+                 if (errs) {
+                     if (serverBump->sslErrors)
+                         cbdataReferenceDone(serverBump->sslErrors);
+                     serverBump->sslErrors = cbdataReference(errs);
+                 }
+             }
+         }
+         anErr =  new ErrorState(ERR_SECURE_CONNECT_FAIL, Http::scServiceUnavailable, request.getRaw());
+         anErr->detail = errDetails;
+         /*anErr->xerrno= Should preserved*/
+     }
+     bail(anErr);
+     if (serverConnection()->getPeer()) {
+         peerConnectFailed(serverConnection()->getPeer());
+     }
+     serverConn->close();
+     return;
+ }
+ /// Checks errors in the cert. validator response against sslproxy_cert_error.
+ /// The first honored error, if any, is returned via errDetails parameter.
+ /// The method returns all seen errors except SSL_ERROR_NONE as Ssl::CertErrors.
+ Ssl::CertErrors *
+ Ssl::PeerConnector::sslCrtvdCheckForErrors(Ssl::CertValidationResponse const &resp, Ssl::ErrorDetail *& errDetails)
+ {
+     Ssl::CertErrors *errs = NULL;
+     ACLFilledChecklist *check = NULL;
+     if (acl_access *acl = ::Config.ssl_client.cert_error)
+         check = new ACLFilledChecklist(acl, request.getRaw(), dash_str);
+     SSL *ssl = fd_table[serverConnection()->fd].ssl;
+     typedef Ssl::CertValidationResponse::RecvdErrors::const_iterator SVCRECI;
+     for (SVCRECI i = resp.errors.begin(); i != resp.errors.end(); ++i) {
+         debugs(83, 7, "Error item: " << i->error_no << " " << i->error_reason);
+         assert(i->error_no != SSL_ERROR_NONE);
+         if (!errDetails) {
+             bool allowed = false;
+             if (check) {
+                 check->sslErrors = new Ssl::CertErrors(Ssl::CertError(i->error_no, i->cert.get()));
+                 if (check->fastCheck() == ACCESS_ALLOWED)
+                     allowed = true;
+             }
+             // else the Config.ssl_client.cert_error access list is not defined
+             // and the first error will cause the error page
+             if (allowed) {
+                 debugs(83, 3, "bypassing SSL error " << i->error_no << " in " << "buffer");
+             } else {
+                 debugs(83, 5, "confirming SSL error " << i->error_no);
+                 X509 *brokenCert = i->cert.get();
+                 Ssl::X509_Pointer peerCert(SSL_get_peer_certificate(ssl));
+                 const char *aReason = i->error_reason.empty() ? NULL : i->error_reason.c_str();
+                 errDetails = new Ssl::ErrorDetail(i->error_no, peerCert.get(), brokenCert, aReason);
+             }
+             if (check) {
+                 delete check->sslErrors;
+                 check->sslErrors = NULL;
+             }
+         }
+         if (!errs)
+             errs = new Ssl::CertErrors(Ssl::CertError(i->error_no, i->cert.get()));
+         else
+             errs->push_back_unique(Ssl::CertError(i->error_no, i->cert.get()));
+     }
+     if (check)
+         delete check;
+     return errs;
+ }
+ /// A wrapper for Comm::SetSelect() notifications.
+ void
+ Ssl::PeerConnector::NegotiateSsl(int, void *data)
+ {
+     PeerConnector *pc = static_cast<PeerConnector*>(data);
+     // Use job calls to add done() checks and other job logic/protections.
+     CallJobHere(83, 7, pc, Ssl::PeerConnector, negotiateSsl);
+ }
+ void
+ Ssl::PeerConnector::handleNegotiateError(const int ret)
+ {
+     const int fd = serverConnection()->fd;
+     unsigned long ssl_lib_error = SSL_ERROR_NONE;
+     SSL *ssl = fd_table[fd].ssl;
+     int ssl_error = SSL_get_error(ssl, ret);
++    BIO *b = SSL_get_rbio(ssl);
++    Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(b->ptr);
+ #ifdef EPROTO
+         int sysErrNo = EPROTO;
+ #else
+         int sysErrNo = EACCES;
+ #endif
+         switch (ssl_error) {
+         case SSL_ERROR_WANT_READ:
+             Comm::SetSelect(fd, COMM_SELECT_READ, &NegotiateSsl, this, 0);
+             return;
+         case SSL_ERROR_WANT_WRITE:
++            if (request->clientConnectionManager->sslBumpMode == Ssl::bumpPeekAndSplice && srvBio->holdWrite()) {
++                debugs(81, DBG_IMPORTANT, "hold write on SSL connection on FD " << fd);
++                checkForPeekAndSplice();
++                return;
++            }
+             Comm::SetSelect(fd, COMM_SELECT_WRITE, &NegotiateSsl, this, 0);
+             return;
+         case SSL_ERROR_SSL:
+         case SSL_ERROR_SYSCALL:
+             ssl_lib_error = ERR_get_error();
+             // store/report errno when ssl_error is SSL_ERROR_SYSCALL, ssl_lib_error is 0, and ret is -1
+             if (ssl_error == SSL_ERROR_SYSCALL && ret == -1 && ssl_lib_error == 0)
+                 sysErrNo = errno;
+             debugs(83, DBG_IMPORTANT, "Error negotiating SSL on FD " << fd <<
+                    ": " << ERR_error_string(ssl_lib_error, NULL) << " (" <<
+                    ssl_error << "/" << ret << "/" << errno << ")");
+             break; // proceed to the general error handling code
+         default:
+             break; // no special error handling for all other errors
+         }
+     ErrorState *const anErr = ErrorState::NewForwarding(ERR_SECURE_CONNECT_FAIL, request.getRaw());
+     anErr->xerrno = sysErrNo;
+     Ssl::ErrorDetail *errFromFailure = (Ssl::ErrorDetail *)SSL_get_ex_data(ssl, ssl_ex_index_ssl_error_detail);
+     if (errFromFailure != NULL) {
+         // The errFromFailure is attached to the ssl object
+         // and will be released when ssl object destroyed.
+         // Copy errFromFailure to a new Ssl::ErrorDetail object
+         anErr->detail = new Ssl::ErrorDetail(*errFromFailure);
+     } else {
+         // server_cert can be NULL here
+         X509 *server_cert = SSL_get_peer_certificate(ssl);
+         anErr->detail = new Ssl::ErrorDetail(SQUID_ERR_SSL_HANDSHAKE, server_cert, NULL);
+         X509_free(server_cert);
+     }
+     if (ssl_lib_error != SSL_ERROR_NONE)
+         anErr->detail->setLibError(ssl_lib_error);
+     if (request->clientConnectionManager.valid()) {
+         // remember the server certificate from the ErrorDetail object
+         if (Ssl::ServerBump *serverBump = request->clientConnectionManager->serverBump()) {
+             serverBump->serverCert.resetAndLock(anErr->detail->peerCert());
+             // remember validation errors, if any
+             if (Ssl::CertErrors *errs = static_cast<Ssl::CertErrors*>(SSL_get_ex_data(ssl, ssl_ex_index_ssl_errors)))
+                 serverBump->sslErrors = cbdataReference(errs);
+         }
+         // 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 = anErr->detail->peerCert()) {
+                 if (const char *name = Ssl::CommonHostName(srvX509)) {
+                     request->SetHost(name);
+                     debugs(83, 3, HERE << "reset request host: " << name);
+                 }
+             }
+         }
+     }
+     bail(anErr);
+ }
+ void
+ Ssl::PeerConnector::bail(ErrorState *error)
+ {
+     Must(error); // or the recepient will not know there was a problem
+     // 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);
+     Must(callback != NULL);
+     CbDialer *dialer = dynamic_cast<CbDialer*>(callback->getDialer());
+     Must(dialer);
+     dialer->answer().error = error;
+     callBack();
+     // Our job is done. The callabck recepient will probably close the failed
+     // peer connection and try another peer or go direct (if possible). We
+     // can close the connection ourselves (our error notification would reach
+     // the recepient before the fd-closure notification), but we would rather
+     // minimize the number of fd-closure notifications and let the recepient
+     // manage the TCP state of the connection.
+ }
+ void
+ Ssl::PeerConnector::callBack()
+ {
+     AsyncCall::Pointer cb = callback;
+     // Do this now so that if we throw below, swanSong() assert that we _tried_
+     // to call back holds.
+     callback = NULL; // this should make done() true
+     // remove close handler
+     comm_remove_close_handler(serverConnection()->fd, closeHandler);
+     CbDialer *dialer = dynamic_cast<CbDialer*>(cb->getDialer());
+     Must(dialer);
+     dialer->answer().conn = serverConnection();
+     ScheduleCallHere(cb);
+ }
+ void
+ Ssl::PeerConnector::swanSong()
+ {
+     // XXX: unregister fd-closure monitoring and CommSetSelect interest, if any
+     AsyncJob::swanSong();
+     assert(!callback); // paranoid: we have not left the caller waiting
+ }
+ const char *
+ Ssl::PeerConnector::status() const
+ {
+     static MemBuf buf;
+     buf.reset();
+     // TODO: redesign AsyncJob::status() API to avoid this
+     // id and stop reason reporting duplication.
+     buf.append(" [", 2);
+     if (stopReason != NULL) {
+         buf.Printf("Stopped, reason:");
+         buf.Printf("%s",stopReason);
+     }
+     if (serverConn != NULL)
+         buf.Printf(" FD %d", serverConn->fd);
+     buf.Printf(" %s%u]", id.Prefix, id.value);
+     buf.terminate();
+     return buf.content();
+ }
+ /* PeerConnectorAnswer */
+ Ssl::PeerConnectorAnswer::~PeerConnectorAnswer()
+ {
+     delete error.get();
+ }
+ std::ostream &
+ operator <<(std::ostream &os, const Ssl::PeerConnectorAnswer &answer)
+ {
+     return os << answer.conn << ", " << answer.error;
+ }
index 0000000000000000000000000000000000000000,820026df24341e104ebde6384f26582dcedc5589..44c94eb51ef9aaaa4bd548c9a57f529e2836798c
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,170 +1,172 @@@
+ /*
+  * SQUID Web Proxy Cache          http://www.squid-cache.org/
+  * ----------------------------------------------------------
+  *
+  *  Squid is the result of efforts by numerous individuals from
+  *  the Internet community; see the CONTRIBUTORS file for full
+  *  details.   Many organizations have provided support for Squid's
+  *  development; see the SPONSORS file for full details.  Squid is
+  *  Copyrighted (C) 2001 by the Regents of the University of
+  *  California; see the COPYRIGHT file for full details.  Squid
+  *  incorporates software developed and/or copyrighted by other
+  *  sources; see the CREDITS file for full details.
+  *
+  *  This program is free software; you can redistribute it and/or modify
+  *  it under the terms of the GNU General Public License as published by
+  *  the Free Software Foundation; either version 2 of the License, or
+  *  (at your option) any later version.
+  *
+  *  This program is distributed in the hope that it will be useful,
+  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  *  GNU General Public License for more details.
+  *
+  *  You should have received a copy of the GNU General Public License
+  *  along with this program; if not, write to the Free Software
+  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
+  *
+  */
+ #ifndef SQUID_SSL_PEER_CONNECTOR_H
+ #define SQUID_SSL_PEER_CONNECTOR_H
+ #include "base/AsyncJob.h"
+ #include "base/AsyncCbdataCalls.h"
+ #include "ssl/support.h"
+ #include <iosfwd>
+ class HttpRequest;
+ class ErrorState;
+ namespace Ssl {
+ class ErrorDetail;
+ class CertValidationResponse;
+ /// PeerConnector results (supplied via a callback).
+ /// The connection to peer was secured if and only if the error member is nil.
+ class PeerConnectorAnswer {
+ public:
+     ~PeerConnectorAnswer(); ///< deletes error if it is still set
+     Comm::ConnectionPointer conn; ///< peer connection (secured on success)
+     /// answer recepients must clear the error member in order to keep its info
+     /// XXX: We should refcount ErrorState instead of cbdata-protecting it.
+     CbcPointer<ErrorState> error; ///< problem details (nil on success)
+ };
+ /**
+  \par
+  * Connects Squid client-side to an SSL peer (cache_peer ... ssl).
+  * Handles peer certificate validation.
+  * Used by TunnelStateData, FwdState, and PeerPoolMgr to start talking to an
+  * SSL peer.
+  \par
+  * The caller receives a call back with PeerConnectorAnswer. If answer.error
+  * is not nil, then there was an error and the SSL connection to the SSL peer
+  * was not fully established. The error object is suitable for error response
+  * generation.
+  \par
+  * The caller must monitor the connection for closure because this
+  * job will not inform the caller about such events.
+  \par
+  * The caller must monitor the overall connection establishment timeout and
+  * close the connection on timeouts. This is probably better than having
+  * dedicated (or none at all!) timeouts for peer selection, DNS lookup,
+  * TCP handshake, SSL handshake, etc. Some steps may have their own timeout,
+  * but not all steps should be forced to have theirs. XXX: Neither tunnel.cc
+  * nor forward.cc have a "overall connection establishment" timeout. We need
+  * to change their code so that they start monitoring earlier and close on
+  * timeouts. This change may need to be discussed on squid-dev.
+  \par
+  * This job never closes the connection, even on errors. If a 3rd-party
+  * closes the connection, this job simply quits without informing the caller.
+ */ 
+ class PeerConnector: virtual public AsyncJob
+ {
+ public:
+     /// Callback dialier API to allow PeerConnector to set the answer.
+     class CbDialer {
+     public:
+         virtual ~CbDialer() {}
+         /// gives PeerConnector access to the in-dialer answer
+         virtual PeerConnectorAnswer &answer() = 0;
+     };
+     typedef RefCount<HttpRequest> HttpRequestPointer;
+ public:
+     PeerConnector(HttpRequestPointer &aRequest,
+                   const Comm::ConnectionPointer &aServerConn,
+                   AsyncCall::Pointer &aCallback);
+     virtual ~PeerConnector();
+ protected:
+     // AsyncJob API
+     virtual void start();
+     virtual bool doneAll() const;
+     virtual void swanSong();
+     virtual const char *status() const;
+     /// The comm_close callback handler.
+     void commCloseHandler(const CommCloseCbParams &params);
+     /// Inform us that the connection is closed. Does the required clean-up.
+     void connectionClosed(const char *reason);
+     /// Sets up TCP socket-related notification callbacks if things go wrong.
+     /// If socket already closed return false, else install the comm_close
+     /// handler to monitor the socket.
+     bool prepareSocket();
+     void initializeSsl(); ///< Initializes SSL state
+     /// Performs a single secure connection negotiation step.
+     /// It is called multiple times untill the negotiation finish or aborted.
+     void negotiateSsl();
++    void checkForPeekAndSplice();
++
+     /// Called when the SSL negotiation step aborted because data needs to
+     /// be transferred to/from SSL server or on error. In the first case
+     /// setups the appropriate Comm::SetSelect handler. In second case
+     /// fill an error and report to the PeerConnector caller.
+     void handleNegotiateError(const int result);
+ private:
+     PeerConnector(const PeerConnector &); // not implemented
+     PeerConnector &operator =(const PeerConnector &); // not implemented
+     /// mimics FwdState to minimize changes to FwdState::initiate/negotiateSsl
+     Comm::ConnectionPointer const &serverConnection() const { return serverConn; }
+     void bail(ErrorState *error); ///< Return an error to the PeerConnector caller 
+     /// Callback the caller class, and pass the ready to communicate secure
+     /// connection or an error if PeerConnector failed.
+     void callBack();
+     /// Process response from cert validator helper
+     void sslCrtvdHandleReply(Ssl::CertValidationResponse const &);
+     /// 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);
+     HttpRequestPointer request; ///< peer connection trigger or cause
+     Comm::ConnectionPointer serverConn; ///< TCP connection to the peer
+     AsyncCall::Pointer callback; ///< we call this with the results
+     AsyncCall::Pointer closeHandler; ///< we call this when the connection closed
+     CBDATA_CLASS2(PeerConnector);
+ };
+ } // namespace Ssl
+ std::ostream &operator <<(std::ostream &os, const Ssl::PeerConnectorAnswer &a);
+ #endif /* SQUID_PEER_CONNECTOR_H */
index 59eb761359ee86ba0799f9cbc0f69d9479f5c5a9,d2274faf395893f58502d48348a9f27ba0535c74..3bcd33864175aaa356c0faf99267ab005f048d33
  
  CBDATA_NAMESPACED_CLASS_INIT(Ssl, ServerBump);
  
 -Ssl::ServerBump::ServerBump(HttpRequest *fakeRequest, StoreEntry *e):
 +Ssl::ServerBump::ServerBump(HttpRequest *fakeRequest, StoreEntry *e, Ssl::BumpMode md):
          request(fakeRequest),
 -        sslErrors(NULL)
 +        sslErrors(NULL),
 +        mode(md)
  {
      debugs(33, 4, HERE << "will peek at " << request->GetHost() << ':' << request->port);
-     const char *uri = urlCanonical(request);
+     const char *uri = urlCanonical(request.getRaw());
      if (e) {
          entry = e;
-         entry->lock();
+         entry->lock("Ssl::ServerBump");
      } else
          entry = storeCreateEntry(uri, uri, request->flags, request->method);
      // We do not need to be a client because the error contents will be used
index b681505507ee1f5e3d5c623f62a3168c3d49ff37,c7955d13be8eadbd1a30bf5fcf5f2879fb44e505..12c7185d2933c58f9c76170cc6c8fceaa905ab64
@@@ -27,8 -27,7 +27,8 @@@ public
      HttpRequest::Pointer request;
      StoreEntry *entry; ///< for receiving Squid-generated error messages
      Ssl::X509_Pointer serverCert; ///< HTTPS server certificate
-     Ssl::Errors *sslErrors; ///< SSL [certificate validation] errors
+     Ssl::CertErrors *sslErrors; ///< SSL [certificate validation] errors
 +    Ssl::BumpMode mode; ///< The SSL server bump mode
  
  private:
      store_client *sc; ///< dummy client to prevent entry trimming
diff --cc src/ssl/bio.cc
index d697a67a0c76e88a9ac7a1bea0a721c4e203228d,0000000000000000000000000000000000000000..9ec0bc95016e95e144bd18db637bfb6482ed94dd
mode 100644,000000..100644
--- /dev/null
@@@ -1,753 -1,0 +1,753 @@@
- #if USE_SSL
 +/*
 + * DEBUG: section 83    SSL accelerator support
 + *
 + */
 +
 +#include "squid.h"
 +#include "ssl/support.h"
 +
 +/* support.cc says this is needed */
++#if USE_OPENSSL
 +
 +#include "comm.h"
 +#include "ip/Address.h"
 +#include "fde.h"
 +#include "globals.h"
 +#include "Mem.h"
 +#include "ssl/bio.h"
 +#if HAVE_OPENSSL_SSL_H
 +#include <openssl/ssl.h>
 +#endif
 +
 +#undef DO_SSLV23
 +
 +// TODO: fde.h should probably export these for wrappers like ours
 +extern int default_read_method(int, char *, int);
 +extern int default_write_method(int, const char *, int);
 +#if _SQUID_WINDOWS_
 +extern int socket_read_method(int, char *, int);
 +extern int socket_write_method(int, const char *, int);
 +#endif
 +
 +/* BIO callbacks */
 +static int squid_bio_write(BIO *h, const char *buf, int num);
 +static int squid_bio_read(BIO *h, char *buf, int size);
 +static int squid_bio_puts(BIO *h, const char *str);
 +//static int squid_bio_gets(BIO *h, char *str, int size);
 +static long squid_bio_ctrl(BIO *h, int cmd, long arg1, void *arg2);
 +static int squid_bio_create(BIO *h);
 +static int squid_bio_destroy(BIO *data);
 +/* SSL callbacks */
 +static void squid_ssl_info(const SSL *ssl, int where, int ret);
 +
 +/// Initialization structure for the BIO table with
 +/// Squid-specific methods and BIO method wrappers.
 +static BIO_METHOD SquidMethods = {
 +    BIO_TYPE_SOCKET,
 +    "squid",
 +    squid_bio_write,
 +    squid_bio_read,
 +    squid_bio_puts,
 +    NULL, // squid_bio_gets not supported
 +    squid_bio_ctrl,
 +    squid_bio_create,
 +    squid_bio_destroy,
 +    NULL // squid_callback_ctrl not supported
 +};
 +
 +BIO *
 +Ssl::Bio::Create(const int fd, Ssl::Bio::Type type)
 +{
 +    if (BIO *bio = BIO_new(&SquidMethods)) {
 +        BIO_int_ctrl(bio, BIO_C_SET_FD, type, fd);
 +        return bio;
 +    }
 +    return NULL;
 +}
 +
 +void
 +Ssl::Bio::Link(SSL *ssl, BIO *bio)
 +{
 +    SSL_set_bio(ssl, bio, bio); // cannot fail
 +    SSL_set_info_callback(ssl, &squid_ssl_info); // does not provide diagnostic
 +}
 +
 +
 +Ssl::Bio::Bio(const int anFd): fd_(anFd)
 +{
 +    debugs(83, 7, "Bio constructed, this=" << this << " FD " << fd_);
 +}
 +
 +Ssl::Bio::~Bio()
 +{
 +    debugs(83, 7, "Bio destructing, this=" << this << " FD " << fd_);
 +}
 +
 +int Ssl::Bio::write(const char *buf, int size, BIO *table)
 +{
 +    errno = 0;
 +#if _SQUID_WINDOWS_
 +    const int result = socket_write_method(fd_, buf, size);
 +#else
 +    const int result = default_write_method(fd_, buf, size);
 +#endif
 +    const int xerrno = errno;
 +    debugs(83, 5, "FD " << fd_ << " wrote " << result << " <= " << size);
 +
 +    BIO_clear_retry_flags(table);
 +    if (result < 0) {
 +        const bool ignoreError = ignoreErrno(xerrno) != 0;
 +        debugs(83, 5, "error: " << xerrno << " ignored: " << ignoreError);
 +        if (ignoreError)
 +            BIO_set_retry_write(table);
 +    }
 +
 +    return result;
 +}
 +
 +int
 +Ssl::Bio::read(char *buf, int size, BIO *table)
 +{
 +    errno = 0;
 +#if _SQUID_WINDOWS_
 +    const int result = socket_read_method(fd_, buf, size);
 +#else
 +    const int result = default_read_method(fd_, buf, size);
 +#endif
 +    const int xerrno = errno;
 +    debugs(83, 5, "FD " << fd_ << " read " << result << " <= " << size);
 +
 +    BIO_clear_retry_flags(table);
 +    if (result < 0) {
 +        const bool ignoreError = ignoreErrno(xerrno) != 0;
 +        debugs(83, 5, "error: " << xerrno << " ignored: " << ignoreError);
 +        if (ignoreError)
 +            BIO_set_retry_read(table);
 +    }
 +
 +    return result;
 +}
 +
 +/// Called whenever the SSL connection state changes, an alert appears, or an
 +/// error occurs. See SSL_set_info_callback().
 +void
 +Ssl::Bio::stateChanged(const SSL *ssl, int where, int ret)
 +{
 +    // Here we can use (where & STATE) to check the current state.
 +    // Many STATE values are possible, including: SSL_CB_CONNECT_LOOP,
 +    // SSL_CB_ACCEPT_LOOP, SSL_CB_HANDSHAKE_START, and SSL_CB_HANDSHAKE_DONE.
 +    // For example:
 +    // if (where & SSL_CB_HANDSHAKE_START)
 +    //    debugs(83, 9, "Trying to establish the SSL connection");
 +    // else if (where & SSL_CB_HANDSHAKE_DONE)
 +    //    debugs(83, 9, "SSL connection established");
 +
 +    debugs(83, 7, "FD " << fd_ << " now: 0x" << std::hex << where << std::dec << ' ' <<
 +           SSL_state_string(ssl) << " (" << SSL_state_string_long(ssl) << ")");
 +}
 +
 +bool
 +Ssl::ClientBio::isClientHello(int state)
 +{
 +    return (state == SSL2_ST_GET_CLIENT_HELLO_A ||
 +            state == SSL3_ST_SR_CLNT_HELLO_A ||
 +            state == SSL23_ST_SR_CLNT_HELLO_A ||
 +            state == SSL23_ST_SR_CLNT_HELLO_B ||
 +            state == SSL3_ST_SR_CLNT_HELLO_B ||
 +            state == SSL3_ST_SR_CLNT_HELLO_C
 +        );
 +}
 +
 +void 
 +Ssl::ClientBio::stateChanged(const SSL *ssl, int where, int ret)
 +{
 +    Ssl::Bio::stateChanged(ssl, where, ret);
 +}
 +
 +int
 +Ssl::ClientBio::write(const char *buf, int size, BIO *table)
 +{
 +    if (holdWrite_) {
 +        BIO_set_retry_write(table);
 +        return 0;
 +    }
 +
 +    return Ssl::Bio::write(buf, size, table);
 +}
 +
 +const char *objToString(unsigned char const *bytes, int len)
 +{
 +    static std::string buf;
 +    buf.clear();
 +    for(int i = 0; i < len; i++ ) {
 +        char tmp[3];
 +        snprintf(tmp, sizeof(tmp), "%.2x", bytes[i]);
 +        buf.append(tmp);
 +    }
 +    return buf.c_str();
 +}
 +
 +int
 +Ssl::ClientBio::read(char *buf, int size, BIO *table)
 +{
 +    if (headerState < 2) {
 +
 +        if (rbuf.isNull())
 +            rbuf.init(1024, 4096);
 +
 +        size = rbuf.spaceSize() > size ? size : rbuf.spaceSize();
 +
 +        if (!size)
 +            return 0;
 +
 +        int bytes = Ssl::Bio::read(buf, size, table);
 +        if (!bytes)
 +            return 0;
 +        rbuf.append(buf, bytes);
 +        debugs(83, 7, "rbuf size: " << rbuf.contentSize());
 +    }
 +
 +    if (headerState == 0) {
 +
 +        const unsigned char *head = (const unsigned char *)rbuf.content();
 +        const char *s = objToString(head, rbuf.contentSize());
 +        debugs(83, 7, "SSL Header: " << s);
 +        if (rbuf.contentSize() < 5) {
 +            BIO_set_retry_read(table);
 +            return 0;
 +        }
 +
 +        if (head[0] == 0x16) {
 +            debugs(83, 7, "SSL version 3 handshake message");
 +            headerBytes = (head[3] << 8) + head[4];
 +            debugs(83, 7, "SSL Header Size: " << headerBytes);
 +#ifdef DO_SSLV23
 +        } else if ((head[0] & 0x80) && head[2] == 0x01 && head[3] == 0x03) { 
 +            debugs(83, 7, "SSL version 2 handshake message with v3 support");
 +            headerBytes = head[1];
 +#endif
 +        }else {
 +            debugs(83, 7, "Not an SSL acceptable handshake message (SSLv2 message?)");
 +            return -1;
 +        }
 +
 +        headerState = 1; //Next state
 +    }
 +
 +    if (headerState == 1) {
 +        const unsigned char *head = (const unsigned char *)rbuf.content();
 +        const char *s = objToString(head, rbuf.contentSize());
 +        debugs(83, 7, "SSL Header: " << s);
 +
 +        if (headerBytes > rbuf.contentSize()) {
 +            BIO_set_retry_read(table);
 +            return -1;
 +        }
 +        features.get((const unsigned char *)rbuf.content());
 +        headerState = 2;
 +    }
 +
 +    if (holdRead_) {
 +        debugs(83, 7, "Hold flag is set, retry latter. (Hold " << size << "bytes)");
 +        BIO_set_retry_read(table);
 +        return -1;
 +    }
 +
 +    if (headerState >=2) {
 +        if (rbuf.hasContent()) {
 +            int bytes = (size <= rbuf.contentSize() ? size : rbuf.contentSize());
 +            memcpy(buf, rbuf.content(), bytes);
 +            rbuf.consume(bytes);
 +            return bytes;
 +        } else
 +            return Ssl::Bio::read(buf, size, table);
 +    }
 +
 +    return -1;
 +}
 +
 +void
 +Ssl::ServerBio::stateChanged(const SSL *ssl, int where, int ret)
 +{
 +    Ssl::Bio::stateChanged(ssl, where, ret);
 +}
 +
 +void
 +Ssl::ServerBio::setClientRandom(const unsigned char *r)
 +{
 +    memcpy(clientRandom, r, SSL3_RANDOM_SIZE);
 +    randomSet = true;
 +};
 +
 +int
 +Ssl::ServerBio::read(char *buf, int size, BIO *table)
 +{
 +    int bytes = Ssl::Bio::read(buf, size, table);
 +
 +    if (bytes > 0 && record_) {
 +        if (rbuf.isNull())
 +            rbuf.init(1024, 8196);
 +        rbuf.append(buf, bytes);
 +    }
 +    return bytes;
 +}
 +
 +int
 +Ssl::ServerBio::write(const char *buf, int size, BIO *table)
 +{
 +
 +    if (holdWrite_) {
 +        debugs(83, 7,  "Hold write, for SSL connection on " << fd_);
 +        BIO_set_retry_write(table);
 +        return -1;
 +    }
 +
 +    if (!helloBuild) {
 +        if (
 +            buf[1] >= 3  //it is an SSL Version3 message
 +            && buf[0] == 0x16 // and it is a Handshake/Hello message
 +            ) {
 +            if (helloMsg.isNull())
 +                helloMsg.init(1024, 4096);
 +
 +            //Hello message is the first message we write to server
 +            assert(!helloMsg.hasContent());
 +
 +            SSL *ssl = fd_table[fd_].ssl;
 +            if (randomSet && ssl && ssl->s3) {
 +                assert(size > 11 + SSL3_RANDOM_SIZE);
 +                helloMsg.append(buf, 11);
 +                //The random number is stored in the 11 position of the 
 +                // message we are going to sent
 +                helloMsg.append((char *)clientRandom, SSL3_RANDOM_SIZE);
 +                size_t len = size - 11 - SSL3_RANDOM_SIZE;
 +                helloMsg.append(buf + 11 + SSL3_RANDOM_SIZE, len);
 +
 +                // We need to fix the random in SSL struct:
 +                memcpy(ssl->s3->client_random, clientRandom, SSL3_RANDOM_SIZE);
 +                // We also need to fix the raw message in SSL struct
 +                // stored in SSL->init_buf. Looks that it is used to get
 +                // digest of the previous sent SSL message, to compute keys
 +                // for encryption/decryption:
 +                memcpy(ssl->init_buf->data + 6, clientRandom, SSL3_RANDOM_SIZE);
 +
 +                debugs(83, 7,  "SSL HELLO message for FD " << fd_ << ": Random number is adjusted");
 +            }
 +        }
 +        helloBuild = true;
 +        helloMsgSize = helloMsg.contentSize();
 +    }
 +
 +    if (helloMsg.hasContent()) {
 +        debugs(83, 7,  "buffered write for FD " << fd_);
 +        int ret = Ssl::Bio::write(helloMsg.content(), helloMsg.contentSize(), table);
 +        helloMsg.consume(ret);
 +        if (helloMsg.hasContent()) {
 +            // We need to retry sendind data.
 +            // Say to openSSL to retry sending hello message
 +            BIO_set_retry_write(table);
 +            return -1;
 +        }
 +
 +        // Sending hello message complete. Do not send more data for now...
 +        holdWrite_ = true; 
 +        // The size should be less than the size of the hello message
 +        assert(size >= helloMsgSize);
 +        return helloMsgSize;
 +    } else
 +        return Ssl::Bio::write(buf, size, table);
 +}
 +
 +void
 +Ssl::ServerBio::flush(BIO *table)
 +{
 +    if (helloMsg.hasContent()) {
 +        int ret = Ssl::Bio::write(helloMsg.content(), helloMsg.contentSize(), table);
 +        helloMsg.consume(ret);
 +    }
 +}
 +
 +/// initializes BIO table after allocation
 +static int
 +squid_bio_create(BIO *bi)
 +{
 +    bi->init = 0; // set when we store Bio object and socket fd (BIO_C_SET_FD)
 +    bi->num = 0;
 +    bi->ptr = NULL;
 +    bi->flags = 0;
 +    return 1;
 +}
 +
 +/// cleans BIO table before deallocation
 +static int
 +squid_bio_destroy(BIO *table)
 +{
 +    delete static_cast<Ssl::Bio*>(table->ptr);
 +    table->ptr = NULL;
 +    return 1;
 +}
 +
 +/// wrapper for Bio::write()
 +static int
 +squid_bio_write(BIO *table, const char *buf, int size)
 +{
 +    Ssl::Bio *bio = static_cast<Ssl::Bio*>(table->ptr);
 +    assert(bio);
 +    return bio->write(buf, size, table);
 +}
 +
 +/// wrapper for Bio::read()
 +static int
 +squid_bio_read(BIO *table, char *buf, int size)
 +{
 +    Ssl::Bio *bio = static_cast<Ssl::Bio*>(table->ptr);
 +    assert(bio);
 +    return bio->read(buf, size, table);
 +}
 +
 +/// implements puts() via write()
 +static int
 +squid_bio_puts(BIO *table, const char *str)
 +{
 +    assert(str);
 +    return squid_bio_write(table, str, strlen(str));
 +}
 +
 +/// other BIO manipulations (those without dedicated callbacks in BIO table)
 +static long
 +squid_bio_ctrl(BIO *table, int cmd, long arg1, void *arg2)
 +{
 +    debugs(83, 5, table << ' ' << cmd << '(' << arg1 << ", " << arg2 << ')');
 +
 +    switch (cmd) {
 +    case BIO_C_SET_FD: {
 +        assert(arg2);
 +        const int fd = *static_cast<int*>(arg2);
 +        Ssl::Bio *bio;
 +        if (arg1 == Ssl::Bio::BIO_TO_SERVER)
 +            bio = new Ssl::ServerBio(fd);
 +        else
 +            bio = new Ssl::ClientBio(fd);
 +        assert(!table->ptr);
 +        table->ptr = bio;
 +        table->init = 1;
 +        return 0;
 +    }
 +
 +    case BIO_C_GET_FD:
 +        if (table->init) {
 +            Ssl::Bio *bio = static_cast<Ssl::Bio*>(table->ptr);
 +            assert(bio);
 +            if (arg2)
 +                *static_cast<int*>(arg2) = bio->fd();
 +            return bio->fd();
 +        }
 +        return -1;
 +
 +    case BIO_CTRL_DUP:
 +        // Should implemented if the SSL_dup openSSL API function 
 +        // used anywhere in squid.
 +        return 0;
 +
 +    case BIO_CTRL_FLUSH:
 +        if (table->init) {
 +            Ssl::Bio *bio = static_cast<Ssl::Bio*>(table->ptr);
 +            assert(bio);
 +            bio->flush(table);
 +            return 1;
 +        }
 +        return 0;
 +
 +/*  we may also need to implement these:
 +    case BIO_CTRL_RESET:
 +    case BIO_C_FILE_SEEK:
 +    case BIO_C_FILE_TELL:
 +    case BIO_CTRL_INFO:
 +    case BIO_CTRL_GET_CLOSE:
 +    case BIO_CTRL_SET_CLOSE:
 +    case BIO_CTRL_PENDING:
 +    case BIO_CTRL_WPENDING:
 +*/
 +    default:
 +        return 0;
 +
 +    }
 +
 +    return 0; /* NOTREACHED */
 +}
 +
 +/// wrapper for Bio::stateChanged()
 +static void
 +squid_ssl_info(const SSL *ssl, int where, int ret)
 +{
 +    if (BIO *table = SSL_get_rbio(ssl)) {
 +        if (Ssl::Bio *bio = static_cast<Ssl::Bio*>(table->ptr))
 +            bio->stateChanged(ssl, where, ret);
 +    }
 +}
 +
 +Ssl::Bio::sslFeatures::sslFeatures(): sslVersion(-1), compressMethod(-1)
 +{
 +    memset(client_random, 0, SSL3_RANDOM_SIZE);
 +}
 +
 +int Ssl::Bio::sslFeatures::toSquidSSLVersion() const
 +{
 +    if(sslVersion == SSL2_VERSION)
 +        return 2;
 +    else if(sslVersion == SSL3_VERSION)
 +        return 3;
 +    else if(sslVersion == TLS1_VERSION)
 +        return 4;
 +#if OPENSSL_VERSION_NUMBER >= 0x10001000L
 +    else if(sslVersion == TLS1_1_VERSION)
 +        return 5;
 +    else if(sslVersion == TLS1_2_VERSION)
 +        return 6;
 +#endif
 +    else
 +        return 1;
 +}
 +
 +bool
 +Ssl::Bio::sslFeatures::get(const SSL *ssl)
 +{
 +    sslVersion = SSL_version(ssl);
 +    debugs(83, 7, "SSL version: " << SSL_get_version(ssl) << " (" << sslVersion << ")");
 +
 +    if(const char *server = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name))
 +        serverName = server;
 +    debugs(83, 7, "SNI server name: " << serverName);
 +
 +    if (ssl->session->compress_meth)
 +            compressMethod = ssl->session->compress_meth;
 +    else if(sslVersion >= 3) //if it is 3 or newer version then compression is disabled
 +        compressMethod = 0;
 +    debugs(83, 7, "SSL compression: " << compressMethod);
 +
 +    STACK_OF(SSL_CIPHER) * ciphers = NULL;
 +    if (ssl->server)
 +        ciphers = ssl->session->ciphers;
 +    else
 +        ciphers = ssl->cipher_list;
 +    if (ciphers) {
 +        for (int i = 0; i < sk_SSL_CIPHER_num(ciphers); ++i) {
 +            SSL_CIPHER *c = sk_SSL_CIPHER_value(ciphers, i);
 +            if (c != NULL) {
 +                if(!clientRequestedCiphers.empty())
 +                    clientRequestedCiphers.append(":");
 +                clientRequestedCiphers.append(c->name);
 +            }
 +        }
 +    }
 +    debugs(83, 7, "Ciphers requested by client: " << clientRequestedCiphers);
 +
 +    if (sslVersion >=3 && ssl->s3 && ssl->s3->client_random[0]) {
 +        memcpy(client_random, ssl->s3->client_random, SSL3_RANDOM_SIZE);
 +    }
 +
 +#if 0 /* XXX: OpenSSL 0.9.8k lacks at least some of these tlsext_* fields */
 +    //The following extracted for logging purpuses:
 +    // TLSEXT_TYPE_ec_point_formats
 +    unsigned char *p;
 +    int len;
 +    if (ssl->server) {
 +        p = ssl->session->tlsext_ecpointformatlist;
 +        len = ssl->session->tlsext_ecpointformatlist_length;
 +    } else {
 +        p = ssl->tlsext_ecpointformatlist;
 +        len = ssl->tlsext_ecpointformatlist_length;
 +    }
 +    if (p) {
 +        ecPointFormatList = objToString(p, len);
 +        debugs(83, 7, "tlsExtension ecPointFormatList of length " << len << " :" << ecPointFormatList);
 +    }
 +
 +    // TLSEXT_TYPE_elliptic_curves
 +    if (ssl->server) {
 +        p = ssl->session->tlsext_ellipticcurvelist;
 +        len = ssl->session->tlsext_ellipticcurvelist_length;
 +    } else {
 +        p = ssl->tlsext_ellipticcurvelist;
 +        len = ssl->tlsext_ellipticcurvelist_length;
 +    }
 +    if (p) {
 +        ellipticCurves = objToString(p, len);
 +        debugs(83, 7, "tlsExtension ellipticCurveList of length " <<  len <<" :" << ellipticCurves);
 +    }
 +    // TLSEXT_TYPE_opaque_prf_input
 +    p = NULL;
 +    if (ssl->server) {
 +        if (ssl->s3 &&  ssl->s3->client_opaque_prf_input) {
 +            p = (unsigned char *)ssl->s3->client_opaque_prf_input;
 +            len = ssl->s3->client_opaque_prf_input_len;
 +        }
 +    } else {
 +        p = (unsigned char *)ssl->tlsext_opaque_prf_input;
 +        len = ssl->tlsext_opaque_prf_input_len;
 +    }
 +    if (p) {
 +        debugs(83, 7, "tlsExtension client-opaque-prf-input of length " << len);
 +        opaquePrf = objToString(p, len);
 +    }
 +#endif
 +    return true;
 +}
 +
 +bool
 +Ssl::Bio::sslFeatures::get(const unsigned char *hello)
 +{
 +    // The SSL handshake message should starts with a 0x16 byte
 +    if (hello[0] == 0x16) {
 +        return parseV3Hello(hello);
 +#ifdef DO_SSLV23
 +    } else if ((hello[0] & 0x80) && hello[2] == 0x01 && hello[3] == 0x03) {
 +        return parseV23Hello(hello);
 +#endif
 +    }
 +    
 +    debugs(83, 7, "Not a known SSL handshake message");
 +    return false;
 +}
 +
 +bool
 +Ssl::Bio::sslFeatures::parseV3Hello(const unsigned char *hello)
 +{
 +    debugs(83, 7, "Get fake features from v3 hello message.");
 +    // The SSL version exist in the 2nd and 3rd bytes
 +    sslVersion = (hello[1] << 8) | hello[2];
 +    debugs(83, 7, "Get fake features. Version :" << std::hex << std::setw(8) << std::setfill('0')<< sslVersion);
 +
 +    // The following hello message size exist in 4th and 5th bytes
 +    int helloSize = (hello[3] << 8) | hello[4];
 +    helloSize += 5; //Include the 5 header bytes.
 +
 +    //For SSLv3 or TLSv1.* protocols we can get some more informations
 +    if (hello[1] == 0x3 && hello[5] == 0x1 /*HELLO A message*/) {
 +        // Get the correct version of the sub-hello message
 +        sslVersion = (hello[9] << 8) | hello[10];
 +        //Get Client Random number. It starts on the position 11 of hello message
 +        memcpy(client_random, hello + 11, SSL3_RANDOM_SIZE);
 +        debugs(83, 7, "Client random: " <<  objToString(client_random, SSL3_RANDOM_SIZE));
 +
 +        // At the position 43 (11+SSL3_RANDOM_SIZE)
 +        int sessIDLen = (int)hello[43];
 +        debugs(83, 7, "Session ID Length: " <<  sessIDLen);
 +
 +        //Ciphers list. It is stored after the Session ID.
 +        const unsigned char *ciphers = hello + 44 + sessIDLen;
 +        int ciphersLen = (ciphers[0] << 8) | ciphers[1];
 +        ciphers += 2;
 +        if (ciphersLen) {
 +            const SSL_METHOD *method = SSLv3_method();
 +            int cs = method->put_cipher_by_char(NULL, NULL);
 +            assert(cs > 0);
 +            for (int i = 0; i < ciphersLen; i += cs) {
 +                const SSL_CIPHER *c = method->get_cipher_by_char((ciphers + i));
 +                if (c != NULL) {
 +                    if(!clientRequestedCiphers.empty())
 +                        clientRequestedCiphers.append(":");
 +                    clientRequestedCiphers.append(c->name);
 +                }
 +            }
 +        }
 +        debugs(83, 7, "Ciphers requested by client: " << clientRequestedCiphers);
 +
 +        // Compression field: 1 bytes the number of compression methods and
 +        // 1 byte for each compression method
 +        const unsigned char *compression = ciphers + ciphersLen;
 +        if (compression[0] > 1)
 +            compressMethod = 1;
 +        else
 +            compressMethod = 0;
 +        debugs(83, 7, "SSL compression methods number: " << (int)compression[0]);
 +
 +        const unsigned char *extensions = compression + 1 + (int)compression[0];
 +        if (extensions <  hello + helloSize) { 
 +            int extensionsLen = (extensions[0] << 8) | extensions[1];
 +            const unsigned char *ext = extensions + 2;
 +            while (ext < extensions+extensionsLen){
 +                short extType = (ext[0] << 8) | ext[1];
 +                ext += 2;
 +                short extLen = (ext[0] << 8) | ext[1];
 +                ext += 2;
 +                debugs(83, 7, "SSL Exntension: " << std::hex << extType << " of size:" << extLen);
 +                //The SNI extension has the type 0 (extType == 0)
 +                // The two first bytes indicates the length of the SNI data (should be extLen-2)
 +                // The next byte is the hostname type, it should be '0' for normal hostname (ext[2] == 0)
 +                // The 3rd and 4th bytes are the length of the hostname
 +                if (extType == 0 && ext[2] == 0) {
 +                    int hostLen = (ext[3] << 8) | ext[4];
 +                    serverName.assign((const char *)(ext+5), hostLen);
 +                    debugs(83, 7, "Found server name: " << serverName);
 +                }
 +                ext += extLen;
 +            }
 +        }
 +    }
 +    return true;
 +}
 +
 +bool
 +Ssl::Bio::sslFeatures::parseV23Hello(const unsigned char *hello)
 +{
 +#ifdef DO_SSLV23
 +    debugs(83, 7, "Get fake features from v23 hello message.");
 +    sslVersion = (hello[3] << 8) | hello[4];
 +    debugs(83, 7, "Get fake features. Version :" << std::hex << std::setw(8) << std::setfill('0')<< sslVersion);
 +
 +    // The following hello message size exist in 2nd byte
 +    int helloSize = hello[1];
 +    helloSize += 2; //Include the 2 header bytes.
 +
 +    //Ciphers list. It is stored after the Session ID.
 +
 +    int ciphersLen = (hello[5] << 8) | hello[6];
 +    const unsigned char *ciphers = hello + 11;
 +    if (ciphersLen) {
 +        const SSL_METHOD *method = SSLv23_method();
 +        int cs = method->put_cipher_by_char(NULL, NULL);
 +        assert(cs > 0);
 +        for (int i = 0; i < ciphersLen; i += cs) {
 +            // The v2 hello messages cipher has 3 bytes.
 +            // The v2 cipher has the first byte not null
 +            // Because we are going to sent only v3 message we 
 +            // are ignoring these ciphers
 +            if (ciphers[i] != 0)
 +                continue;
 +            const SSL_CIPHER *c = method->get_cipher_by_char((ciphers + i + 1));
 +            if (c != NULL) {
 +                if(!clientRequestedCiphers.empty())
 +                    clientRequestedCiphers.append(":");
 +                clientRequestedCiphers.append(c->name);
 +            }
 +        }
 +    }
 +    debugs(83, 7, "Ciphers requested by client: " << clientRequestedCiphers);    
 +
 +    //Get Client Random number. It starts on the position 11 of hello message
 +    memcpy(client_random, ciphers + ciphersLen, SSL3_RANDOM_SIZE);
 +    debugs(83, 7, "Client random: " <<  objToString(client_random, SSL3_RANDOM_SIZE));
 +
 +    compressMethod = 0;
 +    return true;
 +#else
 +    return false;
 +#endif
 +}
 +
 +std::ostream &
 +Ssl::Bio::sslFeatures::print(std::ostream &os) const
 +{
 +    static std::string buf;
 +    return os << "v" << sslVersion << 
 +        " SNI:" << (serverName.empty() ? "-" : serverName) <<
 +        " comp:" << compressMethod <<
 +        " Ciphers:" << clientRequestedCiphers <<
 +        " Random:" << objToString(client_random, SSL3_RANDOM_SIZE) <<
 +        " ecPointFormats:" << ecPointFormatList <<
 +        " ec:" << ellipticCurves <<
 +        " opaquePrf:" << opaquePrf;
 +}
 +
 +#endif /* USE_SSL */
index a6be51a195d8d2d81f7dd23ebf21d6442fa01a6f,f944bf0c4e21353848ce1facf9861daebf67c05c..33566e137a822289b232050fbb4339b729c13865
  
  #include "acl/FilledChecklist.h"
  #include "anyp/PortCfg.h"
 +#include "fd.h"
  #include "fde.h"
  #include "globals.h"
+ #include "ipc/MemMap.h"
  #include "SquidConfig.h"
+ #include "SquidTime.h"
 +#include "ssl/bio.h"
  #include "ssl/Config.h"
  #include "ssl/ErrorDetail.h"
- #include "ssl/support.h"
  #include "ssl/gadgets.h"
+ #include "ssl/support.h"
  #include "URL.h"
  
  #if HAVE_ERRNO_H
@@@ -1077,33 -1071,6 +1141,29 @@@ Ssl::serverMethod(int version
          break;
      }
  
- #if OPENSSL_VERSION_NUMBER < 0x00909000L
-     SSL_METHOD *method;
- #else
-     const SSL_METHOD *method;
- #endif
-     SSL_CTX *sslContext;
 +    //Not reached
 +    return NULL;
 +}
 +
 +SSL_CTX *
 +sslCreateClientContext(const char *certfile, const char *keyfile, int version, const char *cipher, const char *options, const char *flags, const char *CAfile, const char *CApath, const char *CRLfile)
 +{
 +    int ssl_error;
++    Ssl::ContextMethod method;
++    SSL_CTX * sslContext;
 +    long fl = Ssl::parse_flags(flags);
 +
 +    ssl_initialize();
 +
 +    if (!keyfile)
 +        keyfile = certfile;
 +
 +    if (!certfile)
 +        certfile = keyfile;
 +
 +    if (!(method = Ssl::method(version)))
 +        return NULL;
 +        
      sslContext = SSL_CTX_new(method);
  
      if (sslContext == NULL) {
@@@ -1541,51 -1508,17 +1601,60 @@@ Ssl::generateSslContext(CertificateProp
      return createSSLContext(cert, pkey, port);
  }
  
 +bool
 +Ssl::configureSSL(SSL *ssl, CertificateProperties const &properties, AnyP::PortCfg &port)
 +{
 +    Ssl::X509_Pointer cert;
 +    Ssl::EVP_PKEY_Pointer pkey;
 +    if (!generateSslCertificate(cert, pkey, properties))
 +        return false;
 +
 +    if (!cert)
 +        return false;
 +
 +    if (!pkey)
 +        return false;
 +
 +    if (!SSL_use_certificate(ssl, cert.get()))
 +        return false;
 +
 +    if (!SSL_use_PrivateKey(ssl, pkey.get()))
 +        return false;
 +
 +    return true;
 +}
 +
 +bool
 +Ssl::configureSSLUsingPkeyAndCertFromMemory(SSL *ssl, const char *data, AnyP::PortCfg &port)
 +{
 +    Ssl::X509_Pointer cert;
 +    Ssl::EVP_PKEY_Pointer pkey;
 +    if (!readCertAndPrivateKeyFromMemory(cert, pkey, data))
 +        return false;
 +
 +    if (!cert || !pkey)
 +        return false;
 +
 +    if (!SSL_use_certificate(ssl, cert.get()))
 +        return false;
 +
 +    if (!SSL_use_PrivateKey(ssl, pkey.get()))
 +        return false;
 +
 +    return true;
 +}
 +
  bool Ssl::verifySslCertificate(SSL_CTX * sslContext, CertificateProperties const &properties)
  {
+     // SSL_get_certificate is buggy in openssl versions 1.0.1d and 1.0.1e
+     // Try to retrieve certificate directly from SSL_CTX object
+ #if SQUID_USE_SSLGETCERTIFICATE_HACK
+     X509 ***pCert = (X509 ***)sslContext->cert;
+     X509 * cert = pCert && *pCert ? **pCert : NULL;
+ #elif SQUID_SSLGETCERTIFICATE_BUGGY
+     X509 * cert = NULL;
+     assert(0);
+ #else
      // Temporary ssl for getting X509 certificate from SSL_CTX.
      Ssl::SSL_Pointer ssl(SSL_new(sslContext));
      X509 * cert = SSL_get_certificate(ssl.get());
@@@ -1715,47 -1650,228 +1786,270 @@@ bool Ssl::generateUntrustedCert(X509_Po
      return Ssl::generateSslCertificate(untrustedCert, untrustedPkey, certProperties);
  }
  
 +SSL *
 +SslCreate(SSL_CTX *sslContext, const int fd, Ssl::Bio::Type type, const char *squidCtx)
 +{
 +    const char *errAction = NULL;
 +    int errCode = 0;
 +    if (SSL *ssl = SSL_new(sslContext)) {
 +        // without BIO, we would call SSL_set_fd(ssl, fd) instead
 +        if (BIO *bio = Ssl::Bio::Create(fd, type)) {
 +            Ssl::Bio::Link(ssl, bio); // cannot fail
 +
 +            fd_table[fd].ssl = ssl;
 +            fd_table[fd].read_method = &ssl_read_method;
 +            fd_table[fd].write_method = &ssl_write_method;
 +            fd_note(fd, squidCtx);
 +
 +            return ssl;
 +        }
 +        errCode = ERR_get_error();
 +        errAction = "failed to initialize I/O";
 +        SSL_free(ssl);
 +    } else {
 +        errCode = ERR_get_error();
 +        errAction = "failed to allocate handle";
 +    }
 +
 +    debugs(83, DBG_IMPORTANT, "ERROR: " << squidCtx << ' ' << errAction <<
 +           ": " << ERR_error_string(errCode, NULL));
 +    return NULL;
 +}
 +
 +SSL *
 +Ssl::CreateClient(SSL_CTX *sslContext, const int fd, const char *squidCtx)
 +{
 +    return SslCreate(sslContext, fd, Ssl::Bio::BIO_TO_SERVER, squidCtx);
 +}
 +
 +SSL *
 +Ssl::CreateServer(SSL_CTX *sslContext, const int fd, const char *squidCtx)
 +{
 +    return SslCreate(sslContext, fd, Ssl::Bio::BIO_TO_CLIENT, squidCtx);
 +}
 +
+ Ssl::CertError::CertError(ssl_error_t anErr, X509 *aCert): code(anErr)
+ {
+     cert.resetAndLock(aCert);
+ }
+ Ssl::CertError::CertError(CertError const &err): code(err.code)
+ {
+     cert.resetAndLock(err.cert.get());
+ }
+ Ssl::CertError &
+ Ssl::CertError::operator = (const CertError &old)
+ {
+     code = old.code;
+     cert.resetAndLock(old.cert.get());
+     return *this;
+ }
+ bool
+ Ssl::CertError::operator == (const CertError &ce) const
+ {
+     return code == ce.code && cert.get() == ce.cert.get();
+ }
+ bool
+ Ssl::CertError::operator != (const CertError &ce) const
+ {
+     return code != ce.code || cert.get() != ce.cert.get();
+ }
+ static int
+ store_session_cb(SSL *ssl, SSL_SESSION *session)
+ {
+     if (!SslSessionCache)
+         return 0;
+     debugs(83, 5, "Request to store SSL Session ");
+     SSL_SESSION_set_timeout(session, Config.SSL.session_ttl);
+     unsigned char *id = session->session_id;
+     unsigned int idlen = session->session_id_length;
+     unsigned char key[MEMMAP_SLOT_KEY_SIZE];
+     // Session ids are of size 32bytes. They should always fit to a
+     // MemMap::Slot::key
+     assert(idlen <= MEMMAP_SLOT_KEY_SIZE);
+     memset(key, 0, sizeof(key));
+     memcpy(key, id, idlen);
+     int pos;
+     Ipc::MemMap::Slot *slotW = SslSessionCache->openForWriting((const cache_key*)key, pos);
+     if (slotW) {
+         int lenRequired =  i2d_SSL_SESSION(session, NULL);
+         if (lenRequired <  MEMMAP_SLOT_DATA_SIZE) {
+             unsigned char *p = (unsigned char *)slotW->p;
+             lenRequired = i2d_SSL_SESSION(session, &p);
+             slotW->set(key, NULL, lenRequired, squid_curtime + Config.SSL.session_ttl);
+         }
+         SslSessionCache->closeForWriting(pos);
+         debugs(83, 5, "wrote an ssl session entry of size " << lenRequired << " at pos " << pos);
+     }
+     return 0;
+ }
+ static void
+ remove_session_cb(SSL_CTX *, SSL_SESSION *sessionID)
+ {
+     if (!SslSessionCache)
+         return ;
+     debugs(83, 5, "Request to remove corrupted or not valid SSL Session ");
+     int pos;
+     Ipc::MemMap::Slot const *slot = SslSessionCache->openForReading((const cache_key*)sessionID, pos);
+     if (slot == NULL)
+         return;
+     SslSessionCache->closeForReading(pos);
+     // TODO:
+     // What if we are not able to remove the session?
+     // Maybe schedule a job to remove it later?
+     // For now we just have an invalid entry in cache until will be expired
+     // The openSSL will reject it when we try to use it
+     SslSessionCache->free(pos);
+ }
+ static SSL_SESSION *
+ get_session_cb(SSL *, unsigned char *sessionID, int len, int *copy)
+ {
+     if (!SslSessionCache)
+         return NULL;
+     SSL_SESSION *session = NULL;
+     const unsigned int *p;
+     p = (unsigned int *)sessionID;
+     debugs(83, 5, "Request to search for SSL Session of len:" <<
+            len << p[0] << ":" << p[1]);
+     int pos;
+     Ipc::MemMap::Slot const *slot = SslSessionCache->openForReading((const cache_key*)sessionID, pos);
+     if (slot != NULL) {
+         if (slot->expire > squid_curtime) {
+             const unsigned char *ptr = slot->p;
+             session = d2i_SSL_SESSION(NULL, &ptr, slot->pSize);
+             debugs(83, 5, "Session retrieved from cache at pos " << pos);
+         } else
+             debugs(83, 5, "Session in cache expired");
+         SslSessionCache->closeForReading(pos);
+     }
+     if (!session)
+         debugs(83, 5, "Failed to retrieved from cache\n");
+     // With the parameter copy the callback can require the SSL engine
+     // to increment the reference count of the SSL_SESSION object, Normally
+     // the reference count is not incremented and therefore the session must
+     // not be explicitly freed with SSL_SESSION_free(3).
+     *copy = 0;
+     return session;
+ }
+ static void
+ setSessionCallbacks(SSL_CTX *ctx)
+ {
+     if (SslSessionCache) {
+         SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_SERVER|SSL_SESS_CACHE_NO_INTERNAL);
+         SSL_CTX_sess_set_new_cb(ctx, store_session_cb);
+         SSL_CTX_sess_set_remove_cb(ctx, remove_session_cb);
+         SSL_CTX_sess_set_get_cb(ctx, get_session_cb);
+     }
+ }
+ static bool
+ isSslServer()
+ {
+     if (Config.Sockaddr.https)
+         return true;
+     for (AnyP::PortCfg *s = Config.Sockaddr.http; s; s = s->next) {
+         if (s->flags.tunnelSslBumping)
+             return true;
+     }
+     return false;
+ }
+ #define SSL_SESSION_ID_SIZE 32
+ #define SSL_SESSION_MAX_SIZE 10*1024
+ void
+ Ssl::initialize_session_cache()
+ {
+     if (!isSslServer()) //no need to configure ssl session cache.
+         return;
+     // Check if the MemMap keys and data are enough big to hold
+     // session ids and session data
+     assert(SSL_SESSION_ID_SIZE >= MEMMAP_SLOT_KEY_SIZE);
+     assert(SSL_SESSION_MAX_SIZE >= MEMMAP_SLOT_DATA_SIZE);
+     int configuredItems = ::Config.SSL.sessionCacheSize / sizeof(Ipc::MemMap::Slot);
+     if (IamWorkerProcess() && configuredItems)
+         SslSessionCache = new Ipc::MemMap(SslSessionCacheName);
+     else {
+         SslSessionCache = NULL;
+         return;
+     }
+     for (AnyP::PortCfg *s = ::Config.Sockaddr.https; s; s = s->next) {
+         if (s->staticSslContext.get() != NULL)
+             setSessionCallbacks(s->staticSslContext.get());
+     }
+     for (AnyP::PortCfg *s = ::Config.Sockaddr.http; s; s = s->next) {
+         if (s->staticSslContext.get() != NULL)
+             setSessionCallbacks(s->staticSslContext.get());
+     }
+ }
+ void
+ destruct_session_cache()
+ {
+     delete SslSessionCache;
+ }
+ /// initializes shared memory segments used by MemStore
+ class SharedSessionCacheRr: public Ipc::Mem::RegisteredRunner
+ {
+ public:
+     /* RegisteredRunner API */
+     SharedSessionCacheRr(): owner(NULL) {}
+     virtual void useConfig();
+     virtual ~SharedSessionCacheRr();
+ protected:
+     virtual void create();
+ private:
+     Ipc::MemMap::Owner *owner;
+ };
+ RunnerRegistrationEntry(SharedSessionCacheRr);
+ void
+ SharedSessionCacheRr::useConfig()
+ {
+     Ipc::Mem::RegisteredRunner::useConfig();
+ }
+ void
+ SharedSessionCacheRr::create()
+ {
+     if (!isSslServer()) //no need to configure ssl session cache.
+         return;
+     int items;
+     items = Config.SSL.sessionCacheSize / sizeof(Ipc::MemMap::Slot);
+     if (items)
+         owner =  Ipc::MemMap::Init(SslSessionCacheName, items);
+ }
+ SharedSessionCacheRr::~SharedSessionCacheRr()
+ {
+     delete owner;
+ }
  
- #endif /* USE_SSL */
+ #endif /* USE_OPENSSL */
index 61dc1fceecd360abcc13e5f31edb6846e72af408,7198f5bb81ac65d07e7210b7b4a36ce30fe7f137..c5384115705f6cb0c291b45310fdd2f96f8dfe6c
@@@ -74,14 -83,23 +83,31 @@@ typedef int ssl_error_t
  
  typedef CbDataList<Ssl::ssl_error_t> Errors;
  
 +/// Creates SSL Client connection structure and initializes SSL I/O (Comm and BIO).
 +/// On errors, emits DBG_IMPORTANT with details and returns NULL.
 +SSL *CreateClient(SSL_CTX *sslContext, const int fd, const char *squidCtx);
 +
 +/// Creates SSL Server connection structure and initializes SSL I/O (Comm and BIO).
 +/// On errors, emits DBG_IMPORTANT with details and returns NULL.
 +SSL *CreateServer(SSL_CTX *sslContext, const int fd, const char *squidCtx);
 +
+ /// An SSL certificate-related error.
+ /// Pairs an error code with the certificate experiencing the error.
+ class CertError
+ {
+ public:
+     ssl_error_t code; ///< certificate error code
+     X509_Pointer cert; ///< certificate with the above error code
+     CertError(ssl_error_t anErr, X509 *aCert);
+     CertError(CertError const &err);
+     CertError & operator = (const CertError &old);
+     bool operator == (const CertError &ce) const;
+     bool operator != (const CertError &ce) const;
+ };
+ /// Holds a list of certificate SSL errors
+ typedef CbDataList<Ssl::CertError> CertErrors;
  } //namespace Ssl
  
  /// \ingroup ServerProtocolSSLAPI
@@@ -281,15 -278,17 +307,27 @@@ int asn1timeToString(ASN1_TIME *tm, cha
  */
  bool setClientSNI(SSL *ssl, const char *fqdn);
  
 +int OpenSSLtoSquidSSLVersion(int sslVersion);
 +
 +#if OPENSSL_VERSION_NUMBER < 0x00909000L
 +SSL_METHOD *method(int version);
 +#else
 +const SSL_METHOD *method(int version);
 +#endif
 +
 +const SSL_METHOD *serverMethod(int version);
++
+ /**
+    \ingroup ServerProtocolSSLAPI
+    * Initializes the shared session cache if configured
+ */
+ void initialize_session_cache();
+ /**
+    \ingroup ServerProtocolSSLAPI
+    * Destroy the shared session cache if configured
+ */
+ void destruct_session_cache();
  } //namespace Ssl
  
  #if _SQUID_WINDOWS_