]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/FwdState.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / FwdState.cc
index 2e3f1d56b4dac0b3c6310a0bce4580352fc9f9cd..8397fe443575eb5ecbebdc1be83cb4a714e3a846 100644 (file)
@@ -1,35 +1,13 @@
 /*
- * DEBUG: section 17    Request Forwarding
- * AUTHOR: Duane Wessels
- *
- * 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.
+ * 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.
  */
 
+/* DEBUG: section 17    Request Forwarding */
+
 #include "squid.h"
 #include "AccessLogEntry.h"
 #include "acl/AclAddress.h"
@@ -39,6 +17,7 @@
 #include "CacheManager.h"
 #include "CachePeer.h"
 #include "client_side.h"
+#include "clients/forward.h"
 #include "comm/Connection.h"
 #include "comm/ConnOpener.h"
 #include "comm/Loops.h"
@@ -47,7 +26,6 @@
 #include "event.h"
 #include "fd.h"
 #include "fde.h"
-#include "ftp.h"
 #include "FwdState.h"
 #include "globals.h"
 #include "gopher.h"
@@ -64,6 +42,7 @@
 #include "mgr/Registration.h"
 #include "neighbors.h"
 #include "pconn.h"
+#include "PeerPoolMgr.h"
 #include "PeerSelectState.h"
 #include "SquidConfig.h"
 #include "SquidTime.h"
 #include "StoreClient.h"
 #include "urn.h"
 #include "whois.h"
-#if USE_SSL
+#if USE_OPENSSL
 #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"
 #endif
-#if HAVE_ERRNO_H
-#include <errno.h>
-#endif
+
+#include <cerrno>
 
 static PSC fwdPeerSelectionCompleteWrapper;
 static CLCB fwdServerClosedWrapper;
-#if USE_SSL
-static PF fwdNegotiateSSLWrapper;
-#endif
 static CNCB fwdConnectDoneWrapper;
 
 static OBJH fwdStats;
@@ -95,9 +71,35 @@ static OBJH fwdStats;
 #define MAX_FWD_STATS_IDX 9
 static int FwdReplyCodes[MAX_FWD_STATS_IDX + 1][Http::scInvalidHeader + 1];
 
-static PconnPool *fwdPconnPool = new PconnPool("server-side");
+static PconnPool *fwdPconnPool = new PconnPool("server-peers", NULL);
 CBDATA_CLASS_INIT(FwdState);
 
+#if USE_OPENSSL
+class FwdStatePeerAnswerDialer: public CallDialer, public Ssl::PeerConnector::CbDialer
+{
+public:
+    typedef void (FwdState::*Method)(Ssl::PeerConnectorAnswer &);
+
+    FwdStatePeerAnswerDialer(Method method, FwdState *fwd):
+        method_(method), fwd_(fwd), answer_() {}
+
+    /* CallDialer API */
+    virtual bool canDial(AsyncCall &call) { return fwd_.valid(); }
+    void dial(AsyncCall &call) { ((&(*fwd_))->*method_)(answer_); }
+    virtual void print(std::ostream &os) const {
+        os << '(' << fwd_.get() << ", " << answer_ << ')';
+    }
+
+    /* Ssl::PeerConnector::CbDialer API */
+    virtual Ssl::PeerConnectorAnswer &answer() { return answer_; }
+
+private:
+    Method method_;
+    CbcPointer<FwdState> fwd_;
+    Ssl::PeerConnectorAnswer answer_;
+};
+#endif
+
 void
 FwdState::abort(void* d)
 {
@@ -105,21 +107,27 @@ FwdState::abort(void* d)
     Pointer tmp = fwd; // Grab a temporary pointer to keep the object alive during our scope.
 
     if (Comm::IsConnOpen(fwd->serverConnection())) {
-        comm_remove_close_handler(fwd->serverConnection()->fd, fwdServerClosedWrapper, fwd);
-        debugs(17, 3, HERE << "store entry aborted; closing " <<
-               fwd->serverConnection());
-        fwd->serverConnection()->close();
+        fwd->closeServerConnection("store entry aborted");
     } else {
         debugs(17, 7, HERE << "store entry aborted; no connection to close");
     }
-    fwd->serverDestinations.clean();
+    fwd->serverDestinations.clear();
     fwd->self = NULL;
 }
 
+void
+FwdState::closeServerConnection(const char *reason)
+{
+    debugs(17, 3, "because " << reason << "; " << serverConn);
+    comm_remove_close_handler(serverConn->fd, fwdServerClosedWrapper, this);
+    fwdPconnPool->noteUses(fd_table[serverConn->fd].pconn.uses);
+    serverConn->close();
+}
+
 /**** PUBLIC INTERFACE ********************************************************/
 
 FwdState::FwdState(const Comm::ConnectionPointer &client, StoreEntry * e, HttpRequest * r, const AccessLogEntryPointer &alp):
-        al(alp)
+    al(alp)
 {
     debugs(17, 2, HERE << "Forwarding client request " << client << ", url=" << e->url() );
     entry = e;
@@ -129,7 +137,7 @@ FwdState::FwdState(const Comm::ConnectionPointer &client, StoreEntry * e, HttpRe
     pconnRace = raceImpossible;
     start_t = squid_curtime;
     serverDestinations.reserve(Config.forward_max_tries);
-    e->lock();
+    e->lock("FwdState");
     EBIT_SET(e->flags, ENTRY_FWD_HDR_WAIT);
 }
 
@@ -163,7 +171,7 @@ void FwdState::start(Pointer aSelf)
 #endif
 
     // do full route options selection
-    peerSelect(&serverDestinations, request, entry, fwdPeerSelectionCompleteWrapper, this);
+    peerSelect(&serverDestinations, request, al, entry, fwdPeerSelectionCompleteWrapper, this);
 }
 
 #if STRICT_ORIGINAL_DST
@@ -212,6 +220,8 @@ FwdState::completed()
 
     flags.forward_completed = true;
 
+    request->hier.stopPeerClock(false);
+
     if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
         debugs(17, 3, HERE << "entry aborted");
         return ;
@@ -229,7 +239,7 @@ FwdState::completed()
             assert(err);
             errorAppendEntry(entry, err);
             err = NULL;
-#if USE_SSL
+#if USE_OPENSSL
             if (request->flags.sslPeek && request->clientConnectionManager.valid()) {
                 CallJobHere1(17, 4, request->clientConnectionManager, ConnStateData,
                              ConnStateData::httpsPeeked, Comm::ConnectionPointer(NULL));
@@ -262,7 +272,7 @@ FwdState::~FwdState()
 
     entry->unregisterAbort();
 
-    entry->unlock();
+    entry->unlock("FwdState");
 
     entry = NULL;
 
@@ -271,13 +281,10 @@ FwdState::~FwdState()
         calls.connector = NULL;
     }
 
-    if (Comm::IsConnOpen(serverConn)) {
-        comm_remove_close_handler(serverConnection()->fd, fwdServerClosedWrapper, this);
-        debugs(17, 3, HERE << "closing FD " << serverConnection()->fd);
-        serverConn->close();
-    }
+    if (Comm::IsConnOpen(serverConn))
+        closeServerConnection("~FwdState");
 
-    serverDestinations.clean();
+    serverDestinations.clear();
 
     debugs(17, 3, HERE << "FwdState destructor done");
 }
@@ -297,7 +304,7 @@ FwdState::Start(const Comm::ConnectionPointer &clientConn, StoreEntry *entry, Ht
      */
 
     if ( Config.accessList.miss && !request->client_addr.isNoAddr() &&
-            request->protocol != AnyP::PROTO_INTERNAL && request->protocol != AnyP::PROTO_CACHE_OBJECT) {
+            !request->flags.internal && request->url.getScheme() != AnyP::PROTO_CACHE_OBJECT) {
         /**
          * Check if this host is allowed to fetch MISSES from us (miss_access).
          * Intentionally replace the src_addr automatically selected by the checklist code
@@ -313,7 +320,7 @@ FwdState::Start(const Comm::ConnectionPointer &clientConn, StoreEntry *entry, Ht
                 page_id = ERR_FORWARDING_DENIED;
 
             ErrorState *anErr = new ErrorState(page_id, Http::scForbidden, request);
-            errorAppendEntry(entry, anErr);    // frees anErr
+            errorAppendEntry(entry, anErr); // frees anErr
             return;
         }
     }
@@ -333,17 +340,20 @@ FwdState::Start(const Comm::ConnectionPointer &clientConn, StoreEntry *entry, Ht
     if (shutting_down) {
         /* more yuck */
         ErrorState *anErr = new ErrorState(ERR_SHUTTING_DOWN, Http::scServiceUnavailable, request);
-        errorAppendEntry(entry, anErr);        // frees anErr
+        errorAppendEntry(entry, anErr); // frees anErr
         return;
     }
 
-    switch (request->protocol) {
-
-    case AnyP::PROTO_INTERNAL:
+    if (request->flags.internal) {
+        debugs(17, 2, "calling internalStart() due to request flag");
         internalStart(clientConn, request, entry);
         return;
+    }
+
+    switch (request->url.getScheme()) {
 
     case AnyP::PROTO_CACHE_OBJECT:
+        debugs(17, 2, "calling CacheManager due to request scheme " << request->url.getScheme());
         CacheManager::GetInstance()->Start(clientConn, request, entry);
         return;
 
@@ -437,7 +447,7 @@ FwdState::unregister(Comm::ConnectionPointer &conn)
     serverConn = NULL;
 }
 
-// Legacy method to be removed in favor of the above as soon as possible
+// \deprecated use unregister(Comm::ConnectionPointer &conn) instead
 void
 FwdState::unregister(int fd)
 {
@@ -447,7 +457,7 @@ FwdState::unregister(int fd)
 }
 
 /**
- * server-side modules call fwdComplete() when they are done
+ * FooClient modules call fwdComplete() when they are done
  * downloading an object.  Then, we either 1) re-forward the
  * request somewhere else if needed, or 2) call storeComplete()
  * to finish it off
@@ -472,7 +482,7 @@ FwdState::complete()
         entry->reset();
 
         // drop the last path off the selection list. try the next one.
-        serverDestinations.shift();
+        serverDestinations.erase(serverDestinations.begin());
         startConnectionOrFail();
 
     } else {
@@ -493,7 +503,7 @@ FwdState::complete()
 /**** CALLBACK WRAPPERS ************************************************************/
 
 static void
-fwdPeerSelectionCompleteWrapper(Comm::ConnectionList * unused, ErrorState *err, void *data)
+fwdPeerSelectionCompleteWrapper(Comm::ConnectionList *, ErrorState *err, void *data)
 {
     FwdState *fwd = (FwdState *) data;
     if (err)
@@ -508,18 +518,8 @@ fwdServerClosedWrapper(const CommCloseCbParams &params)
     fwd->serverClosed(params.fd);
 }
 
-#if USE_SSL
-static void
-fwdNegotiateSSLWrapper(int fd, void *data)
-{
-    FwdState *fwd = (FwdState *) data;
-    fwd->negotiateSSL(fd);
-}
-
-#endif
-
 void
-fwdConnectDoneWrapper(const Comm::ConnectionPointer &conn, comm_err_t status, int xerrno, void *data)
+fwdConnectDoneWrapper(const Comm::ConnectionPointer &conn, Comm::Flag status, int xerrno, void *data)
 {
     FwdState *fwd = (FwdState *) data;
     fwd->connectDone(conn, status, xerrno);
@@ -532,7 +532,7 @@ fwdConnectDoneWrapper(const Comm::ConnectionPointer &conn, comm_err_t status, in
  *
  * Return TRUE if the request SHOULD be retried.  This method is
  * called when the HTTP connection fails, or when the connection
- * is closed before server-side read the end of HTTP headers.
+ * is closed before reading the end of HTTP headers from the server.
  */
 bool
 FwdState::checkRetry()
@@ -598,7 +598,11 @@ FwdState::checkRetriable()
 void
 FwdState::serverClosed(int fd)
 {
-    debugs(17, 2, HERE << "FD " << fd << " " << entry->url());
+    // XXX: fd is often -1 here
+    debugs(17, 2, "FD " << fd << " " << entry->url() << " after " <<
+           (fd >= 0 ? fd_table[fd].pconn.uses : -1) << " requests");
+    if (fd >= 0 && serverConnection()->fd == fd)
+        fwdPconnPool->noteUses(fd_table[fd].pconn.uses);
     retryOrBail();
 }
 
@@ -611,7 +615,7 @@ FwdState::retryOrBail()
         if (pconnRace == raceHappened)
             debugs(17, 4, HERE << "retrying the same destination");
         else
-            serverDestinations.shift(); // last one failed. try another.
+            serverDestinations.erase(serverDestinations.begin()); // last one failed. try another.
         startConnectionOrFail();
         return;
     }
@@ -619,12 +623,14 @@ FwdState::retryOrBail()
     // TODO: should we call completed() here and move doneWithRetries there?
     doneWithRetries();
 
+    request->hier.stopPeerClock(false);
+
     if (self != NULL && !err && shutting_down) {
         ErrorState *anErr = new ErrorState(ERR_SHUTTING_DOWN, Http::scServiceUnavailable, request);
         errorAppendEntry(entry, anErr);
     }
 
-    self = NULL;       // refcounted
+    self = NULL;    // refcounted
 }
 
 // If the Server quits before nibbling at the request body, the body sender
@@ -646,366 +652,10 @@ FwdState::handleUnregisteredServerEnd()
     retryOrBail();
 }
 
-#if USE_SSL
-void
-FwdState::negotiateSSL(int fd)
-{
-    unsigned long ssl_lib_error = SSL_ERROR_NONE;
-    SSL *ssl = fd_table[fd].ssl;
-    int ret;
-
-    if ((ret = SSL_connect(ssl)) <= 0) {
-        int ssl_error = SSL_get_error(ssl, ret);
-#ifdef EPROTO
-        int sysErrNo = EPROTO;
-#else
-        int sysErrNo = EACCES;
-#endif
-
-        switch (ssl_error) {
-
-        case SSL_ERROR_WANT_READ:
-            Comm::SetSelect(fd, COMM_SELECT_READ, fwdNegotiateSSLWrapper, this, 0);
-            return;
-
-        case SSL_ERROR_WANT_WRITE:
-            Comm::SetSelect(fd, COMM_SELECT_WRITE, fwdNegotiateSSLWrapper, this, 0);
-            return;
-
-        case SSL_ERROR_SSL:
-        case SSL_ERROR_SYSCALL:
-            ssl_lib_error = ERR_get_error();
-            debugs(81, DBG_IMPORTANT, "fwdNegotiateSSL: Error negotiating SSL connection on FD " << fd <<
-                   ": " << ERR_error_string(ssl_lib_error, NULL) << " (" << ssl_error <<
-                   "/" << ret << "/" << errno << ")");
-
-            // 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;
-
-            // falling through to complete error handling
-
-        default:
-            // TODO: move into a method before merge
-            Ssl::ErrorDetail *errDetails;
-            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.
-                errDetails = new Ssl::ErrorDetail(*errFromFailure);
-            } else {
-                // server_cert can be NULL here
-                X509 *server_cert = SSL_get_peer_certificate(ssl);
-                errDetails = new Ssl::ErrorDetail(SQUID_ERR_SSL_HANDSHAKE, server_cert, NULL);
-                X509_free(server_cert);
-            }
-
-            if (ssl_lib_error != SSL_ERROR_NONE)
-                errDetails->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(errDetails->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 = errDetails->peerCert()) {
-                    if (const char *name = Ssl::CommonHostName(srvX509)) {
-                        request->SetHost(name);
-                        debugs(83, 3, HERE << "reset request host: " << name);
-                    }
-                }
-            }
-
-            ErrorState *const anErr = makeConnectingError(ERR_SECURE_CONNECT_FAIL);
-            anErr->xerrno = sysErrNo;
-            anErr->detail = errDetails;
-            fail(anErr);
-
-            if (serverConnection()->getPeer()) {
-                peerConnectFailed(serverConnection()->getPeer());
-            }
-
-            serverConn->close();
-            return;
-        }
-    }
-
-    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(33, 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);
-            fail(anErr);
-            if (serverConnection()->getPeer()) {
-                peerConnectFailed(serverConnection()->getPeer());
-            }
-            serverConn->close();
-            self = NULL;
-            return;
-        }
-    }
-
-    dispatch();
-}
-
 void
-FwdState::sslCrtvdHandleReplyWrapper(void *data, Ssl::CertValidationResponse const &validationResponse)
+FwdState::connectDone(const Comm::ConnectionPointer &conn, Comm::Flag status, int xerrno)
 {
-    FwdState * fwd = (FwdState *)(data);
-    fwd->sslCrtvdHandleReply(validationResponse);
-}
-
-void
-FwdState::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) {
-        dispatch();
-        return;
-    }
-
-    ErrorState *anErr = NULL;
-    if (validatorFailed) {
-        anErr = new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request);
-    }  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 = makeConnectingError(ERR_SECURE_CONNECT_FAIL);
-        anErr->detail = errDetails;
-        /*anErr->xerrno= Should preserved*/
-    }
-
-    fail(anErr);
-    if (serverConnection()->getPeer()) {
-        peerConnectFailed(serverConnection()->getPeer());
-    }
-    serverConn->close();
-    self = NULL;
-    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 *
-FwdState::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, 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;
-}
-
-void
-FwdState::initiateSSL()
-{
-    SSL *ssl;
-    SSL_CTX *sslContext = NULL;
-    const CachePeer *peer = serverConnection()->getPeer();
-    int fd = serverConnection()->fd;
-
-    if (peer) {
-        assert(peer->use_ssl);
-        sslContext = peer->sslContext;
-    } else {
-        sslContext = Config.ssl_client.sslContext;
-    }
-
-    assert(sslContext);
-
-    if ((ssl = SSL_new(sslContext)) == NULL) {
-        debugs(83, DBG_IMPORTANT, "fwdInitiateSSL: Error allocating handle: " << ERR_error_string(ERR_get_error(), NULL)  );
-        ErrorState *anErr = new ErrorState(ERR_SOCKET_FAILURE, Http::scInternalServerError, request);
-        // TODO: create Ssl::ErrorDetail with OpenSSL-supplied error code
-        fail(anErr);
-        self = NULL;           // refcounted
-        return;
-    }
-
-    SSL_set_fd(ssl, fd);
-
-    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 {
-        // 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->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, dash_str);
-            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);
-    }
-
-    fd_table[fd].ssl = ssl;
-    fd_table[fd].read_method = &ssl_read_method;
-    fd_table[fd].write_method = &ssl_write_method;
-    negotiateSSL(fd);
-}
-
-#endif
-
-void
-FwdState::connectDone(const Comm::ConnectionPointer &conn, comm_err_t status, int xerrno)
-{
-    if (status != COMM_OK) {
+    if (status != Comm::OK) {
         ErrorState *const anErr = makeConnectingError(ERR_CONNECT_FAIL);
         anErr->xerrno = xerrno;
         fail(anErr);
@@ -1031,20 +681,48 @@ FwdState::connectDone(const Comm::ConnectionPointer &conn, comm_err_t status, in
     if (serverConnection()->getPeer())
         peerConnectSucceded(serverConnection()->getPeer());
 
-#if USE_SSL
+#if USE_OPENSSL
     if (!request->flags.pinned) {
         if ((serverConnection()->getPeer() && serverConnection()->getPeer()->use_ssl) ||
-                (!serverConnection()->getPeer() && request->protocol == AnyP::PROTO_HTTPS) ||
+                (!serverConnection()->getPeer() && request->url.getScheme() == AnyP::PROTO_HTTPS) ||
                 request->flags.sslPeek) {
-            initiateSSL();
+
+            HttpRequest::Pointer requestPointer = request;
+            AsyncCall::Pointer callback = asyncCall(17,4,
+                                                    "FwdState::ConnectedToPeer",
+                                                    FwdStatePeerAnswerDialer(&FwdState::connectedToPeer, this));
+            // Use positive timeout when less than one second is left.
+            const time_t sslNegotiationTimeout = max(static_cast<time_t>(1), timeLeft());
+            Ssl::PeerConnector *connector =
+                new Ssl::PeerConnector(requestPointer, serverConnection(), clientConn, callback, sslNegotiationTimeout);
+            AsyncJob::Start(connector); // will call our callback
             return;
         }
     }
 #endif
 
+    // should reach ConnStateData before the dispatched Client job starts
+    CallJobHere1(17, 4, request->clientConnectionManager, ConnStateData,
+                 ConnStateData::notePeerConnection, serverConnection());
+
     dispatch();
 }
 
+#if USE_OPENSSL
+void
+FwdState::connectedToPeer(Ssl::PeerConnectorAnswer &answer)
+{
+    if (ErrorState *error = answer.error.get()) {
+        fail(error);
+        answer.error.clear(); // preserve error for errorSendComplete()
+        self = NULL;
+        return;
+    }
+
+    dispatch();
+}
+#endif
+
 void
 FwdState::connectTimeout(int fd)
 {
@@ -1053,7 +731,7 @@ FwdState::connectTimeout(int fd)
     assert(fd == serverDestinations[0]->fd);
 
     if (entry->isEmpty()) {
-        ErrorState *anErr = new ErrorState(ERR_CONNECT_FAIL, Http::scGateway_Timeout, request);
+        ErrorState *anErr = new ErrorState(ERR_CONNECT_FAIL, Http::scGatewayTimeout, request);
         anErr->xerrno = ETIMEDOUT;
         fail(anErr);
 
@@ -1067,21 +745,9 @@ FwdState::connectTimeout(int fd)
     }
 }
 
-/**
- * Called after Forwarding path selection (via peer select) has taken place.
- * And whenever forwarding needs to attempt a new connection (routing failover)
- * We have a vector of possible localIP->remoteIP paths now ready to start being connected.
- */
-void
-FwdState::connectStart()
+time_t
+FwdState::timeLeft() const
 {
-    assert(serverDestinations.size() > 0);
-
-    debugs(17, 3, "fwdConnectStart: " << entry->url());
-
-    if (!request->hier.first_conn_start.tv_sec) // first attempt
-        request->hier.first_conn_start = current_time;
-
     /* connection timeout */
     int ctimeout;
     if (serverDestinations[0]->getPeer()) {
@@ -1097,7 +763,24 @@ FwdState::connectStart()
         ftimeout = 5;
 
     if (ftimeout < ctimeout)
-        ctimeout = ftimeout;
+        return (time_t)ftimeout;
+    else
+        return (time_t)ctimeout;
+}
+
+/**
+ * Called after forwarding path selection (via peer select) has taken place
+ * and whenever forwarding needs to attempt a new connection (routing failover).
+ * We have a vector of possible localIP->remoteIP paths now ready to start being connected.
+ */
+void
+FwdState::connectStart()
+{
+    assert(serverDestinations.size() > 0);
+
+    debugs(17, 3, "fwdConnectStart: " << entry->url());
+
+    request->hier.startPeerClock();
 
     if (serverDestinations[0]->getPeer() && request->flags.sslBumped) {
         debugs(50, 4, "fwdConnectStart: Ssl bumped connections through parent proxy are not allowed");
@@ -1115,7 +798,7 @@ FwdState::connectStart()
         debugs(17,7, "pinned peer connection: " << pinned_connection);
         // pinned_connection may become nil after a pconn race
         if (pinned_connection)
-            serverConn = pinned_connection->validatePinnedConnection(request, serverDestinations[0]->getPeer());
+            serverConn = pinned_connection->borrowPinnedConnection(request, serverDestinations[0]->getPeer());
         else
             serverConn = NULL;
         if (Comm::IsConnOpen(serverConn)) {
@@ -1127,6 +810,20 @@ FwdState::connectStart()
             if (pinned_connection->pinnedAuth())
                 request->flags.auth = true;
             comm_add_close_handler(serverConn->fd, fwdServerClosedWrapper, this);
+
+            /* Update server side TOS and Netfilter mark on the connection. */
+            if (Ip::Qos::TheConfig.isAclTosActive()) {
+                debugs(17, 3, HERE << "setting tos for pinned connection to " << (int)serverConn->tos );
+                serverConn->tos = GetTosToServer(request);
+                Ip::Qos::setSockTos(serverConn, serverConn->tos);
+            }
+#if SO_MARK
+            if (Ip::Qos::TheConfig.isAclNfmarkActive()) {
+                serverConn->nfmark = GetNfmarkToServer(request);
+                Ip::Qos::setSockNfmark(serverConn, serverConn->nfmark);
+            }
+#endif
+
             // the server may close the pinned connection before this request
             pconnRace = racePossible;
             dispatch();
@@ -1150,7 +847,7 @@ FwdState::connectStart()
     // This does not increase the total number of connections because we just
     // closed the connection that failed the race. And re-pinning assumes this.
     if (pconnRace != raceHappened)
-        temp = fwdPconnPool->pop(serverDestinations[0], host, checkRetriable());
+        temp = pconnPop(serverDestinations[0], host);
 
     const bool openedPconn = Comm::IsConnOpen(temp);
     pconnRace = openedPconn ? racePossible : raceImpossible;
@@ -1190,21 +887,10 @@ FwdState::connectStart()
     entry->mem_obj->checkUrlChecksum();
 #endif
 
-    /* Get the server side TOS and Netfilter mark to be set on the connection. */
-    if (Ip::Qos::TheConfig.isAclTosActive()) {
-        serverDestinations[0]->tos = GetTosToServer(request);
-    }
-#if SO_MARK && USE_LIBCAP
-    serverDestinations[0]->nfmark = GetNfmarkToServer(request);
-    debugs(17, 3, "fwdConnectStart: got outgoing addr " << serverDestinations[0]->local << ", tos " << int(serverDestinations[0]->tos)
-           << ", netfilter mark " << serverDestinations[0]->nfmark);
-#else
-    serverDestinations[0]->nfmark = 0;
-    debugs(17, 3, "fwdConnectStart: got outgoing addr " << serverDestinations[0]->local << ", tos " << int(serverDestinations[0]->tos));
-#endif
+    GetMarkingsToServer(request, *serverDestinations[0]);
 
     calls.connector = commCbCall(17,3, "fwdConnectDoneWrapper", CommConnectCbPtrFun(fwdConnectDoneWrapper, this));
-    Comm::ConnOpener *cs = new Comm::ConnOpener(serverDestinations[0], calls.connector, ctimeout);
+    Comm::ConnOpener *cs = new Comm::ConnOpener(serverDestinations[0], calls.connector, timeLeft());
     if (host)
         cs->setHost(host);
     AsyncJob::Start(cs);
@@ -1213,7 +899,7 @@ FwdState::connectStart()
 void
 FwdState::dispatch()
 {
-    debugs(17, 3, HERE << clientConn << ": Fetching '" << RequestMethodStr(request->method) << " " << entry->url() << "'");
+    debugs(17, 3, clientConn << ": Fetching " << request->method << ' ' << entry->url());
     /*
      * Assert that server_fd is set.  This is to guarantee that fwdState
      * is attached to something and will be deallocated when server_fd
@@ -1223,12 +909,12 @@ FwdState::dispatch()
 
     fd_note(serverConnection()->fd, entry->url());
 
-    fd_table[serverConnection()->fd].noteUse(fwdPconnPool);
+    fd_table[serverConnection()->fd].noteUse();
 
     /*assert(!EBIT_TEST(entry->flags, ENTRY_DISPATCHED)); */
     assert(entry->ping_status != PING_WAITING);
 
-    assert(entry->lock_count);
+    assert(entry->locked());
 
     EBIT_SET(entry->flags, ENTRY_DISPATCHED);
 
@@ -1257,7 +943,7 @@ FwdState::dispatch()
     }
 #endif
 
-#if USE_SSL
+#if USE_OPENSSL
     if (request->flags.sslPeek) {
         CallJobHere1(17, 4, request->clientConnectionManager, ConnStateData,
                      ConnStateData::httpsPeeked, serverConnection());
@@ -1277,8 +963,8 @@ FwdState::dispatch()
         request->peer_login = NULL;
         request->peer_domain = NULL;
 
-        switch (request->protocol) {
-#if USE_SSL
+        switch (request->url.getScheme()) {
+#if USE_OPENSSL
 
         case AnyP::PROTO_HTTPS:
             httpStart(this);
@@ -1294,13 +980,14 @@ FwdState::dispatch()
             break;
 
         case AnyP::PROTO_FTP:
-            ftpStart(this);
+            if (request->flags.ftpNative)
+                Ftp::StartRelay(this);
+            else
+                Ftp::StartGateway(this);
             break;
 
         case AnyP::PROTO_CACHE_OBJECT:
 
-        case AnyP::PROTO_INTERNAL:
-
         case AnyP::PROTO_URN:
             fatal_dump("Should never get here");
             break;
@@ -1309,7 +996,7 @@ FwdState::dispatch()
             whoisStart(this);
             break;
 
-        case AnyP::PROTO_WAIS: /* Not implemented */
+        case AnyP::PROTO_WAIS:  /* Not implemented */
 
         default:
             debugs(17, DBG_IMPORTANT, "WARNING: Cannot retrieve '" << entry->url() << "'.");
@@ -1330,7 +1017,7 @@ FwdState::dispatch()
  *
  * returns TRUE if the transaction SHOULD be re-forwarded to the
  * next choice in the serverDestinations list.  This method is called when
- * server-side communication completes normally, or experiences
+ * peer communication completes normally, or experiences
  * some error after receiving the end of HTTP headers.
  */
 int
@@ -1384,7 +1071,7 @@ ErrorState *
 FwdState::makeConnectingError(const err_type type) const
 {
     return new ErrorState(type, request->flags.needValidation ?
-                          Http::scGateway_Timeout : Http::scServiceUnavailable, request);
+                          Http::scGatewayTimeout : Http::scServiceUnavailable, request);
 }
 
 static void
@@ -1423,7 +1110,7 @@ FwdState::reforwardableStatus(const Http::StatusCode s) const
 
     case Http::scBadGateway:
 
-    case Http::scGateway_Timeout:
+    case Http::scGatewayTimeout:
         return true;
 
     case Http::scForbidden:
@@ -1458,6 +1145,22 @@ FwdState::pconnPush(Comm::ConnectionPointer &conn, const char *domain)
     }
 }
 
+Comm::ConnectionPointer
+FwdState::pconnPop(const Comm::ConnectionPointer &dest, const char *domain)
+{
+    // always call shared pool first because we need to close an idle
+    // connection there if we have to use a standby connection.
+    Comm::ConnectionPointer conn = fwdPconnPool->pop(dest, domain, checkRetriable());
+    if (!Comm::IsConnOpen(conn)) {
+        // either there was no pconn to pop or this is not a retriable xaction
+        if (CachePeer *peer = dest->getPeer()) {
+            if (peer->standby.pool)
+                conn = peer->standby.pool->pop(dest, domain, true);
+        }
+    }
+    return conn; // open, closed, or nil
+}
+
 void
 FwdState::initModule()
 {
@@ -1583,3 +1286,21 @@ GetNfmarkToServer(HttpRequest * request)
     ACLFilledChecklist ch(NULL, request, NULL);
     return aclMapNfmark(Ip::Qos::TheConfig.nfmarkToServer, &ch);
 }
+
+void
+GetMarkingsToServer(HttpRequest * request, Comm::Connection &conn)
+{
+    // Get the server side TOS and Netfilter mark to be set on the connection.
+    if (Ip::Qos::TheConfig.isAclTosActive()) {
+        conn.tos = GetTosToServer(request);
+        debugs(17, 3, "from " << conn.local << " tos " << int(conn.tos));
+    }
+
+#if SO_MARK && USE_LIBCAP
+    conn.nfmark = GetNfmarkToServer(request);
+    debugs(17, 3, "from " << conn.local << " netfilter mark " << conn.nfmark);
+#else
+    conn.nfmark = 0;
+#endif
+}
+