]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
Secure ICAP
authorChristos Tsantilas <chtsanti@users.sourceforge.net>
Tue, 5 May 2015 09:09:27 +0000 (12:09 +0300)
committerChristos Tsantilas <chtsanti@users.sourceforge.net>
Tue, 5 May 2015 09:09:27 +0000 (12:09 +0300)
This patch adds support for ICAP services that require SSL/TLS transport
connections. The same options used for the cache_peer directive are used for
the icap_service directive, with similar certificate validation logic.

To mark an ICAP service as "secure", use an "icaps://" service URI scheme when
listing your service via an icap_service directive. The industry is using a
"Secure ICAP" term, and Squid follows that convention, but "icaps" seems more
appropriate for a _scheme_ name.

Squid uses port 11344 for Secure ICAP by default, following another popular
proxy convention. The old 1344 default for plain ICAP ports has not changed.

Technical Details
==================

This patch:
  - Splits Ssl::PeerConnector class into Ssl::PeerConnector parent and two kids:
    Ssl::BlindPeerConnector, a basic SSL connector for cache_peers, and
    Ssl::PeekingPeerConnector, a peek-and-splice SSL connector for HTTP servers.

  - Adds a third Ssl::IcapPeerConnector kid to connect to Secure ICAP servers.

  - Fixes ErrorState class to avoid crashes on nil ErrorState::request member.
    (Ssl::IcapPeerConnector may generate an ErrorState with a nil request).

  - Modifies the ACL peername to use the Secure ICAP server name as value while
    connecting to an ICAP server. This is useful to make SSL certificate
    policies based on ICAP server name. However, this change is undocumented
    until we decide whether a dedicated ACL would be better.

This is a Measurement Factory project.

17 files changed:
src/FwdState.cc
src/PeerPoolMgr.cc
src/acl/FilledChecklist.cc
src/acl/FilledChecklist.h
src/acl/PeerName.cc
src/adaptation/ServiceConfig.cc
src/adaptation/ServiceConfig.h
src/adaptation/icap/ServiceRep.cc
src/adaptation/icap/ServiceRep.h
src/adaptation/icap/Xaction.cc
src/adaptation/icap/Xaction.h
src/cf.data.pre
src/err_detail_type.h
src/errorpage.cc
src/ssl/PeerConnector.cc
src/ssl/PeerConnector.h
src/tunnel.cc

index 8a23111de1208f978ec2309155556c5e371bbe09..95c64fbfb81a0ecc0ef29d0cd8229f9f86ddbf9f 100644 (file)
@@ -695,8 +695,8 @@ FwdState::connectDone(const Comm::ConnectionPointer &conn, Comm::Flag status, in
                                                     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);
+            Ssl::PeekingPeerConnector *connector =
+                new Ssl::PeekingPeerConnector(requestPointer, serverConnection(), clientConn, callback, sslNegotiationTimeout);
             AsyncJob::Start(connector); // will call our callback
             return;
         }
@@ -1251,7 +1251,7 @@ getOutgoingAddress(HttpRequest * request, Comm::ConnectionPointer conn)
     }
 
     ACLFilledChecklist ch(NULL, request, NULL);
-    ch.dst_peer = conn->getPeer();
+    ch.dst_peer_name = conn->getPeer() ? conn->getPeer()->name : NULL;
     ch.dst_addr = conn->remote;
 
     // TODO use the connection details in ACL.
index 52d9bb236e07c710ef95f66c165ca63a5f4bcf9e..49cf370aa77840302e9a8a06e8ede81c648368b9 100644 (file)
@@ -129,8 +129,8 @@ PeerPoolMgr::handleOpenedConnection(const CommConnectCbParams &params)
         const int timeUsed = squid_curtime - params.conn->startTime();
         // Use positive timeout when less than one second is left for conn.
         const int timeLeft = max(1, (peerTimeout - timeUsed));
-        Ssl::PeerConnector *connector =
-            new Ssl::PeerConnector(request, params.conn, NULL, securer, timeLeft);
+        Ssl::BlindPeerConnector *connector =
+            new Ssl::BlindPeerConnector(request, params.conn, securer, timeLeft);
         AsyncJob::Start(connector); // will call our callback
         return;
     }
index 0db8f54f9acaae237a6dd133d3292cc922134e1f..b66f609904a780853474ab092f84a70674fa694c 100644 (file)
@@ -23,7 +23,6 @@
 CBDATA_CLASS_INIT(ACLFilledChecklist);
 
 ACLFilledChecklist::ACLFilledChecklist() :
-    dst_peer(NULL),
     dst_rdns(NULL),
     request (NULL),
     reply (NULL),
@@ -136,7 +135,6 @@ ACLFilledChecklist::markSourceDomainChecked()
  *    checkCallback() will delete the list (i.e., self).
  */
 ACLFilledChecklist::ACLFilledChecklist(const acl_access *A, HttpRequest *http_request, const char *ident):
-    dst_peer(NULL),
     dst_rdns(NULL),
     request(NULL),
     reply(NULL),
index 09c06f511a023a1a1b22847c52649206f5e758ba..641272513e246c30cb0bb6ed10937591f7e6f0e0 100644 (file)
@@ -67,7 +67,7 @@ public:
     Ip::Address src_addr;
     Ip::Address dst_addr;
     Ip::Address my_addr;
-    CachePeer *dst_peer;
+    SBuf dst_peer_name;
     char *dst_rdns;
 
     HttpRequest *request;
index 816e66d05062b27a28a696bde8718e601249f2ad..247b9c4e7b44ef8ae97fa2cae8398ec92dea7bb5 100644 (file)
@@ -16,8 +16,8 @@
 int
 ACLPeerNameStrategy::match (ACLData<MatchType> * &data, ACLFilledChecklist *checklist, ACLFlags &)
 {
-    if (checklist->dst_peer != NULL && checklist->dst_peer->name != NULL)
-        return data->match(checklist->dst_peer->name);
+    if (!checklist->dst_peer_name.isEmpty())
+        return data->match(checklist->dst_peer_name.c_str());
     return 0;
 }
 
index 8cbce85e426b33aea5a8599b993d75f058ac09d6..3b4338dc4da2aef4cef1275ff18cf2dcb8434ef9 100644 (file)
@@ -127,6 +127,18 @@ Adaptation::ServiceConfig::parse()
         else if (strcmp(name, "on-overload") == 0) {
             grokked = grokOnOverload(onOverload, value);
             onOverloadSet = true;
+        } else if (strncmp(name, "ssl", 3) == 0 || strncmp(name, "tls-", 4) == 0) {
+#if !USE_OPENSSL
+            debugs(3, DBG_PARSE_NOTE(DBG_IMPORTANT), "WARNING: adaptation option '" << token << "' requires --with-openssl. ICAP service option ignored.");
+#else
+            // name prefix is "ssl" or "tls-"
+            std::string tmp = name + (name[0] == 's' ? 3 : 4);
+            tmp += "=";
+            tmp += value;
+            secure.parse(tmp.c_str());
+            secure.encryptTransport = true;
+            grokked = true;
+#endif
         } else
             grokked = grokExtension(name, value);
 
@@ -214,6 +226,10 @@ Adaptation::ServiceConfig::grokUri(const char *value)
     }
 
     host.limitInit(s, len);
+#if USE_OPENSSL
+    if (secure.sslDomain.isEmpty())
+        secure.sslDomain.assign(host.rawBuf(), host.size());
+#endif
     s = e;
 
     port = -1;
index 71df6c328772cb947bdfc42be41081db98b3de18..b1e51df4be1f40336b64613883db958c0d8755d0 100644 (file)
@@ -11,6 +11,7 @@
 
 #include "adaptation/Elements.h"
 #include "base/RefCount.h"
+#include "security/PeerOptions.h"
 #include "SquidString.h"
 
 namespace Adaptation
@@ -47,6 +48,9 @@ public:
     bool routing; ///< whether this service may determine the next service(s)
     bool ipv6;    ///< whether this service uses IPv6 transport (default IPv4)
 
+    // security settings for adaptation service
+    Security::PeerOptions secure;
+
 protected:
     Method parseMethod(const char *buf) const;
     VectPoint parseVectPoint(const char *buf) const;
index 0c8053e9da599ba7bcc8ab17ef06508c073b6dfc..221fe4d76805cb501a2e22156b29becfa527a46b 100644 (file)
@@ -26,6 +26,9 @@
 #include "SquidConfig.h"
 #include "SquidTime.h"
 
+#define DEFAULT_ICAP_PORT   1344
+#define DEFAULT_ICAPS_PORT 11344
+
 CBDATA_NAMESPACED_CLASS_INIT(Adaptation::Icap, ServiceRep);
 
 Adaptation::Icap::ServiceRep::ServiceRep(const ServiceConfigPointer &svcCfg):
@@ -59,15 +62,28 @@ Adaptation::Icap::ServiceRep::finalize()
     // use /etc/services or default port if needed
     const bool have_port = cfg().port >= 0;
     if (!have_port) {
-        struct servent *serv = getservbyname("icap", "tcp");
+        struct servent *serv;
+        if (cfg().protocol.caseCmp("icaps") == 0)
+            serv = getservbyname("icaps", "tcp");
+        else
+            serv = getservbyname("icap", "tcp");
 
         if (serv) {
             writeableCfg().port = htons(serv->s_port);
         } else {
-            writeableCfg().port = 1344;
+            writeableCfg().port = cfg().protocol.caseCmp("icaps") == 0 ? DEFAULT_ICAPS_PORT : DEFAULT_ICAP_PORT;
         }
     }
 
+    if (cfg().protocol.caseCmp("icaps") == 0)
+        writeableCfg().secure.encryptTransport = true;
+
+    if (cfg().secure.encryptTransport) {
+        debugs(3, DBG_IMPORTANT, "Initializing service " << cfg().resource << " SSL context");
+        sslContext = writeableCfg().secure.createContext(true);
+    }
+
+
     theSessionFailures.configure(TheConfig.oldest_service_failure > 0 ?
                                  TheConfig.oldest_service_failure : -1);
 }
index 13102de0d33936033522e1060ba2642293bf557e..a03b9ad8d0ecb980118f17d176d4f1e5b72eb4d4 100644 (file)
@@ -110,6 +110,11 @@ public: // treat these as private, they are for callbacks only
     // receive either an ICAP OPTIONS response header or an abort message
     virtual void noteAdaptationAnswer(const Answer &answer);
 
+    Security::ContextPointer sslContext;
+#if USE_OPENSSL
+    SSL_SESSION *sslSession;
+#endif
+
 private:
     // stores Prepare() callback info
 
index 62afdccc7a021f06f1bb01a4f7674f9fdfe21bd4..215fbaba0f8f94dfae7e74c1471a0fc1cb7e2a9e 100644 (file)
@@ -9,6 +9,7 @@
 /* DEBUG: section 93    ICAP (RFC 3507) Client */
 
 #include "squid.h"
+#include "acl/FilledChecklist.h"
 #include "adaptation/icap/Config.h"
 #include "adaptation/icap/Launcher.h"
 #include "adaptation/icap/Xaction.h"
@@ -21,6 +22,7 @@
 #include "CommCalls.h"
 #include "err_detail_type.h"
 #include "fde.h"
+#include "globals.h"
 #include "FwdState.h"
 #include "HttpMsg.h"
 #include "HttpReply.h"
 #include "SquidConfig.h"
 #include "SquidTime.h"
 
+#if USE_OPENSSL
+/// Gives Ssl::PeerConnector access to Answer in the PeerPoolMgr callback dialer.
+class MyIcapAnswerDialer: public UnaryMemFunT<Adaptation::Icap::Xaction, Security::EncryptorAnswer, Security::EncryptorAnswer&>,
+    public Ssl::PeerConnector::CbDialer
+{
+public:
+    MyIcapAnswerDialer(const JobPointer &aJob, Method aMethod):
+        UnaryMemFunT<Adaptation::Icap::Xaction, Security::EncryptorAnswer, Security::EncryptorAnswer&>(aJob, aMethod, Security::EncryptorAnswer()) {}
+
+    /* Ssl::PeerConnector::CbDialer API */
+    virtual Security::EncryptorAnswer &answer() { return arg1; }
+};
+
+namespace Ssl
+{
+/// A simple PeerConnector for Secure ICAP services. No SslBump capabilities.
+class IcapPeerConnector: public PeerConnector {
+    CBDATA_CLASS(IcapPeerConnector);
+public:
+    IcapPeerConnector(
+        Adaptation::Icap::ServiceRep::Pointer &service,
+        const Comm::ConnectionPointer &aServerConn,
+        AsyncCall::Pointer &aCallback, const time_t timeout = 0):
+        AsyncJob("Ssl::IcapPeerConnector"),
+        PeerConnector(aServerConn, aCallback, timeout), icapService(service) {}
+
+    /* PeerConnector API */
+    virtual SSL *initializeSsl();
+    virtual void noteNegotiationDone(ErrorState *error);
+    virtual SSL_CTX *getSslContext() {return icapService->sslContext; }
+
+private:
+    Adaptation::Icap::ServiceRep::Pointer icapService;
+};
+} // namespace Ssl
+
+CBDATA_NAMESPACED_CLASS_INIT(Ssl, IcapPeerConnector);
+#endif
+
 Adaptation::Icap::Xaction::Xaction(const char *aTypeName, Adaptation::Icap::ServiceRep::Pointer &aService):
     AsyncJob(aTypeName),
     Adaptation::Initiate(aTypeName),
@@ -252,6 +293,23 @@ void Adaptation::Icap::Xaction::noteCommConnected(const CommConnectCbParams &io)
                         CloseDialer(this,&Adaptation::Icap::Xaction::noteCommClosed));
     comm_add_close_handler(io.conn->fd, closer);
 
+#if USE_OPENSSL
+    // If it is a reused connection and the SSL object is build
+    // we should not negotiate new SSL session
+    SSL *ssl = fd_table[io.conn->fd].ssl;
+    if (!ssl && service().cfg().secure.encryptTransport) {
+        CbcPointer<Adaptation::Icap::Xaction> me(this);
+        securer = asyncCall(93, 4, "Adaptation::Icap::Xaction::handleSecuredPeer",
+                            MyIcapAnswerDialer(me, &Adaptation::Icap::Xaction::handleSecuredPeer));
+
+        Ssl::PeerConnector::HttpRequestPointer tmpReq(NULL);
+        Ssl::IcapPeerConnector *sslConnector =
+            new Ssl::IcapPeerConnector(theService, io.conn, securer, TheConfig.connect_timeout(service().cfg().bypass));
+        AsyncJob::Start(sslConnector); // will call our callback
+        return;
+    }
+#endif
+
 // ??    fd_table[io.conn->fd].noteUse(icapPconnPool);
     service().noteConnectionUse(connection);
 
@@ -323,6 +381,10 @@ void Adaptation::Icap::Xaction::handleCommTimedout()
 // unexpected connection close while talking to the ICAP service
 void Adaptation::Icap::Xaction::noteCommClosed(const CommCloseCbParams &)
 {
+    if (securer != NULL) {
+        securer->cancel("Connection closed before SSL negotiation finished");
+        securer = NULL;
+    }
     closer = NULL;
     handleCommClosed();
 }
@@ -351,7 +413,7 @@ void Adaptation::Icap::Xaction::callEnd()
 
 bool Adaptation::Icap::Xaction::doneAll() const
 {
-    return !connector && !reader && !writer && Adaptation::Initiate::doneAll();
+    return !connector && !securer && !reader && !writer && Adaptation::Initiate::doneAll();
 }
 
 void Adaptation::Icap::Xaction::updateTimeout()
@@ -637,3 +699,73 @@ bool Adaptation::Icap::Xaction::fillVirginHttpHeader(MemBuf &) const
     return false;
 }
 
+#if USE_OPENSSL
+SSL *
+Ssl::IcapPeerConnector::initializeSsl()
+{
+    SSL *ssl = Ssl::PeerConnector::initializeSsl();
+    if (!ssl)
+        return NULL;
+
+    assert(!icapService->cfg().secure.sslDomain.isEmpty());
+    SBuf *host = new SBuf(icapService->cfg().secure.sslDomain);
+    SSL_set_ex_data(ssl, ssl_ex_index_server, host);
+
+    ACLFilledChecklist *check = (ACLFilledChecklist *)SSL_get_ex_data(ssl, ssl_ex_index_cert_error_check);
+    if (check)
+        check->dst_peer_name = *host;
+
+    if (icapService->sslSession)
+        SSL_set_session(ssl, icapService->sslSession);
+
+    return ssl;
+}
+
+void
+Ssl::IcapPeerConnector::noteNegotiationDone(ErrorState *error)
+{
+    if (error)
+        return;
+
+    const int fd = serverConnection()->fd;
+    SSL *ssl = fd_table[fd].ssl;
+    assert(ssl);
+    if (!SSL_session_reused(ssl)) {
+        if (icapService->sslSession)
+            SSL_SESSION_free(icapService->sslSession);
+        icapService->sslSession = SSL_get1_session(ssl);
+    }
+}
+
+void
+Adaptation::Icap::Xaction::handleSecuredPeer(Security::EncryptorAnswer &answer)
+{
+    Must(securer != NULL);
+    securer = NULL;
+
+    if (closer != NULL) {
+        if (answer.conn != NULL)
+            comm_remove_close_handler(answer.conn->fd, closer);
+        else
+            closer->cancel("securing completed");
+        closer = NULL;
+    }
+
+    if (answer.error.get()) {
+        if (answer.conn != NULL)
+            answer.conn->close();
+        debugs(93, 2, typeName <<
+               " SSL negotiation to " << service().cfg().uri << " failed");
+        service().noteConnectionFailed("failure");
+        detailError(ERR_DETAIL_ICAP_XACT_SSL_START);
+        throw TexcHere("cannot connect to the SSL ICAP service");
+    }
+
+    debugs(93, 5, "SSL negotiation to " << service().cfg().uri << " complete");
+
+    service().noteConnectionUse(answer.conn);
+
+    handleCommConnected();
+}
+#endif
+
index 2f5945741bb171bebd35e67d63b6b1b27f7ff248..2f2134e23ca8d0f0f9afdf67d693866cc166fe32 100644 (file)
@@ -17,6 +17,7 @@
 #include "HttpReply.h"
 #include "ipcache.h"
 #include "SBuf.h"
+#include "ssl/PeerConnector.h"
 
 class MemBuf;
 
@@ -72,6 +73,7 @@ protected:
     virtual void handleCommTimedout();
     virtual void handleCommClosed();
 
+    void handleSecuredPeer(Security::EncryptorAnswer &answer);
     /// record error detail if possible
     virtual void detailError(int) {}
 
@@ -153,6 +155,7 @@ protected:
 
 private:
     Comm::ConnOpener *cs;
+    AsyncCall::Pointer securer; ///< whether we are securing a connection
 };
 
 } // namespace Icap
index 642ca00cc4c7a90b780ff0656bdf5aad2fe99eb2..a044926c9ff2cdaa80d2df3f155ab182f293deb7 100644 (file)
@@ -8370,6 +8370,12 @@ DOC_START
 
        uri: icap://servername:port/servicepath
                ICAP server and service location.
+            icaps://servername:port/servicepath
+               The "icap:" URI scheme is used for traditional ICAP server and
+               service location (default port is 1344, connections are not
+               encrypted). The "icaps:" URI scheme is for Secure ICAP
+               services that use SSL/TLS-encrypted ICAP connections (by
+               default, on port 11344).
 
        ICAP does not allow a single service to handle both REQMOD and RESPMOD
        transactions. Squid does not enforce that requirement. You can specify
@@ -8435,12 +8441,81 @@ DOC_START
                Use the given number as the Max-Connections limit, regardless
                of the Max-Connections value given by the service, if any.
 
+       ==== SSL / ICAPS / TLS OPTIONS ====
+
+       These options are used for Secure ICAP (icaps://....) services only.
+
+       sslcert=/path/to/ssl/certificate
+                       A client SSL certificate to use when connecting to
+                       this icap server.
+
+       sslkey=/path/to/ssl/key
+                       The private SSL key corresponding to sslcert above.
+                       If 'sslkey' is not specified 'sslcert' is assumed to
+                       reference a combined file containing both the
+                       certificate and the key.
+
+       sslversion=1|3|4|5|6
+                       The SSL version to use when connecting to this icap
+                       server
+                               1 = automatic (default)
+                               3 = SSL v3 only
+                               4 = TLS v1.0 only
+                               5 = TLS v1.1 only
+                               6 = TLS v1.2 only
+
+       sslcipher=...   The list of valid SSL ciphers to use when connecting
+                       to this icap server.
+
+       ssloptions=...  Specify various SSL implementation options:
+
+                           NO_SSLv3    Disallow the use of SSLv3
+                           NO_TLSv1    Disallow the use of TLSv1.0
+                           NO_TLSv1_1  Disallow the use of TLSv1.1
+                           NO_TLSv1_2  Disallow the use of TLSv1.2
+                           SINGLE_DH_USE
+                                     Always create a new key when using
+                                     temporary/ephemeral DH key exchanges
+                           ALL       Enable various bug workarounds
+                           suggested as "harmless" by OpenSSL
+                           Be warned that this reduces SSL/TLS
+                           strength to some attacks.
+
+                       See the OpenSSL SSL_CTX_set_options documentation for a
+                       more complete list.
+
+       sslcafile=...   A file containing additional CA certificates to use
+                       when verifying the icap server certificate.
+
+       sslcapath=...   A directory containing additional CA certificates to
+                       use when verifying the icap server certificate.
+
+       sslcrlfile=...  A certificate revocation list file to use when
+                       verifying the icap server certificate.
+
+       sslflags=...    Specify various flags modifying the SSL implementation:
+
+                       DONT_VERIFY_PEER
+                               Accept certificates even if they fail to
+                               verify.
+                       NO_DEFAULT_CA
+                               Don't use the default CA list built in
+                               to OpenSSL.
+                       DONT_VERIFY_DOMAIN
+                               Don't verify the icap server certificate
+                               matches the server name
+
+       ssldomain=      The icap server name as advertised in it's certificate.
+                       Used for verifying the correctness of the received icap
+                       server certificate. If not specified the icap server
+                       hostname extracted from ICAP URI will be used.
+
        Older icap_service format without optional named parameters is
        deprecated but supported for backward compatibility.
 
 Example:
 icap_service svcBlocker reqmod_precache icap://icap1.mydomain.net:1344/reqmod bypass=0
-icap_service svcLogger reqmod_precache icap://icap2.mydomain.net:1344/respmod routing=on
+icap_service svcLogger reqmod_precache icaps://icap2.mydomain.net:11344/reqmod routing=on
 DOC_END
 
 NAME: icap_class
index a430c7af60add10ff614885b6af3106bed70e64c..5aee6e0c3e06df3a2050df4c7e035bd86cd86882 100644 (file)
@@ -23,6 +23,7 @@ typedef enum {
     ERR_DETAIL_RESPMOD_BLOCK_EARLY, // RESPMOD denied client access to HTTP response, before any part of the response was sent
     ERR_DETAIL_RESPMOD_BLOCK_LATE, // RESPMOD denied client access to HTTP response, after [a part of] the response was sent
     ERR_DETAIL_ICAP_XACT_START, // transaction start failure
+    ERR_DETAIL_ICAP_XACT_SSL_START, // transaction start failure
     ERR_DETAIL_ICAP_XACT_BODY_CONSUMER_ABORT, // transaction body consumer gone
     ERR_DETAIL_ICAP_INIT_GONE, // initiator gone
     ERR_DETAIL_ICAP_XACT_CLOSE, // ICAP connection closed unexpectedly
index 06ad38d84f03df46e9b68028f41c59757d8bfda6..0e8b2e3dafad84f75d8641bf2b105cea0f49eb1c 100644 (file)
@@ -955,8 +955,11 @@ ErrorState::Convert(char token, bool building_deny_info_url, bool allowRecursion
 
     case 'R':
         if (building_deny_info_url) {
-            p = (request->urlpath.size() != 0 ? request->urlpath.termedBuf() : "/");
-            no_urlescape = 1;
+            if (request != NULL) {
+                p = (request->urlpath.size() != 0 ? request->urlpath.termedBuf() : "/");
+                no_urlescape = 1;
+            } else
+                p = "[no request]";
             break;
         }
         if (NULL != request) {
@@ -1147,7 +1150,7 @@ ErrorState::BuildHttpReply()
             status = httpStatus;
         else {
             // Use 307 for HTTP/1.1 non-GET/HEAD requests.
-            if (request->method != Http::METHOD_GET && request->method != Http::METHOD_HEAD && request->http_ver >= Http::ProtocolVersion(1,1))
+            if (request != NULL && request->method != Http::METHOD_GET && request->method != Http::METHOD_HEAD && request->http_ver >= Http::ProtocolVersion(1,1))
                 status = Http::scTemporaryRedirect;
         }
 
index 9ea15253560f09a85ce097d1145b1239aa7dfcc4..336076ed3c9b7ea918c9f10c040aa4f88dbdab4e 100644 (file)
 #include "ssl/support.h"
 
 CBDATA_NAMESPACED_CLASS_INIT(Ssl, PeerConnector);
+CBDATA_NAMESPACED_CLASS_INIT(Ssl, BlindPeerConnector);
+CBDATA_NAMESPACED_CLASS_INIT(Ssl, PeekingPeerConnector);
 
 Ssl::PeerConnector::PeerConnector(
-    HttpRequestPointer &aRequest,
     const Comm::ConnectionPointer &aServerConn,
-    const Comm::ConnectionPointer &aClientConn,
     AsyncCall::Pointer &aCallback,
     const time_t timeout):
     AsyncJob("Ssl::PeerConnector"),
-    request(aRequest),
     serverConn(aServerConn),
-    clientConn(aClientConn),
     callback(aCallback),
     negotiationTimeout(timeout),
     startTime(squid_curtime),
-    splice(false),
-    resumingSession(false),
-    serverCertificateHandled(false)
+    useCertValidator_(false)
 {
     // if this throws, the caller's cb dialer is not our CbDialer
     Must(dynamic_cast<CbDialer*>(callback->getDialer()));
@@ -55,6 +51,7 @@ Ssl::PeerConnector::PeerConnector(
 
 Ssl::PeerConnector::~PeerConnector()
 {
+    cbdataReferenceDone(certErrors);
     debugs(83, 5, "Peer connector " << this << " gone");
 }
 
@@ -69,10 +66,8 @@ Ssl::PeerConnector::start()
 {
     AsyncJob::start();
 
-    if (prepareSocket()) {
-        initializeSsl();
+    if (prepareSocket() && (initializeSsl() != NULL))
         negotiateSsl();
-    }
 }
 
 void
@@ -105,100 +100,23 @@ Ssl::PeerConnector::prepareSocket()
     return true;
 }
 
-void
+SSL *
 Ssl::PeerConnector::initializeSsl()
 {
-    SSL_CTX *sslContext = NULL;
-    const CachePeer *peer = serverConnection()->getPeer();
-    const int fd = serverConnection()->fd;
-
-    if (peer) {
-        assert(peer->secure.encryptTransport);
-        sslContext = peer->sslContext;
-    } else {
-        // XXX: locate a per-server context in Security:: instead
-        sslContext = ::Config.ssl_client.sslContext;
-    }
-
+    SSL_CTX *sslContext = getSslContext();
     assert(sslContext);
 
+    const int fd = serverConnection()->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;
-    }
-
-    if (peer) {
-        // NP: domain may be a raw-IP but it is now always set
-        assert(!peer->secure.sslDomain.isEmpty());
-
-        // const loss is okay here, ssl_ex_index_server is only read and not assigned a destructor
-        SBuf *host = new SBuf(peer->secure.sslDomain);
-        SSL_set_ex_data(ssl, ssl_ex_index_server, host);
-
-        if (peer->sslSession)
-            SSL_set_session(ssl, peer->sslSession);
-    } else if (ConnStateData *csd = request->clientConnectionManager.valid()) {
-        // client connection is required in the case we need to splice
-        // or terminate client and server connections
-        assert(clientConn != NULL);
-        SBuf *hostName = NULL;
-        Ssl::ClientBio *cltBio = NULL;
-
-        //Enable Status_request tls extension, required to bump some clients
-        SSL_set_tlsext_status_type(ssl, TLSEXT_STATUSTYPE_ocsp);
-
-        // In server-first bumping mode, clientSsl is NULL.
-        if (SSL *clientSsl = fd_table[clientConn->fd].ssl) {
-            BIO *b = SSL_get_rbio(clientSsl);
-            cltBio = static_cast<Ssl::ClientBio *>(b->ptr);
-            const Ssl::Bio::sslFeatures &features = cltBio->getFeatures();
-            if (!features.serverName.isEmpty())
-                hostName = new SBuf(features.serverName);
-        }
-
-        if (!hostName) {
-            // While we are peeking at the certificate, we may not know the server
-            // name that the client will request (after interception or CONNECT)
-            // unless it was the CONNECT request with a user-typed address.
-            const bool isConnectRequest = !csd->port->flags.isIntercepted();
-            if (!request->flags.sslPeek || isConnectRequest)
-                hostName = new SBuf(request->GetHost());
-        }
-
-        if (hostName)
-            SSL_set_ex_data(ssl, ssl_ex_index_server, (void*)hostName);
 
-        Must(!csd->serverBump() || csd->serverBump()->step <= Ssl::bumpStep2);
-        if (csd->sslBumpMode == Ssl::bumpPeek || csd->sslBumpMode == Ssl::bumpStare) {
-            assert(cltBio);
-            const Ssl::Bio::sslFeatures &features = cltBio->getFeatures();
-            if (features.sslVersion != -1) {
-                features.applyToSSL(ssl, csd->sslBumpMode);
-                // Should we allow it for all protocols?
-                if (features.sslVersion >= 3) {
-                    BIO *b = SSL_get_rbio(ssl);
-                    Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(b->ptr);
-                    // Inherite client features, like SSL version, SNI and other
-                    srvBio->setClientFeatures(features);
-                    srvBio->recordInput(true);
-                    srvBio->mode(csd->sslBumpMode);
-                }
-            }
-        } else {
-            // Set client SSL options
-            SSL_set_options(ssl, ::Security::ProxyOutgoingConfig.parsedOptions);
-
-            // Use SNI TLS extension only when we connect directly
-            // to the origin server and we know the server host name.
-            const char *sniServer = hostName ? hostName->c_str() :
-                                    (!request->GetHostIsNumeric() ? request->GetHost() : NULL);
-            if (sniServer)
-                Ssl::setClientSNI(ssl, sniServer);
-        }
+        noteNegotiationDone(anErr);
+        bail(anErr);
+        return NULL;
     }
 
     // If CertValidation Helper used do not lookup checklist for errors,
@@ -212,15 +130,7 @@ Ssl::PeerConnector::initializeSsl()
             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);
-    }
+    return ssl;
 }
 
 void
@@ -251,67 +161,19 @@ Ssl::PeerConnector::negotiateSsl()
         return; // we might be gone by now
     }
 
-    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 (!sslFinalized())
         return;
 
     callBack();
 }
 
-void
-Ssl::PeerConnector::handleServerCertificate()
-{
-    if (serverCertificateHandled)
-        return;
-
-    if (ConnStateData *csd = request->clientConnectionManager.valid()) {
-        const int fd = serverConnection()->fd;
-        SSL *ssl = fd_table[fd].ssl;
-        Ssl::X509_Pointer serverCert(SSL_get_peer_certificate(ssl));
-        if (!serverCert.get())
-            return;
-
-        serverCertificateHandled = true;
-
-        csd->resetSslCommonName(Ssl::CommonHostName(serverCert.get()));
-        debugs(83, 5, "HTTPS server CN: " << csd->sslCommonName() <<
-               " bumped: " << *serverConnection());
-
-        // remember the server certificate for later use
-        if (Ssl::ServerBump *serverBump = csd->serverBump()) {
-            serverBump->serverCert.reset(serverCert.release());
-        }
-    }
-}
-
 bool
 Ssl::PeerConnector::sslFinalized()
 {
-    const int fd = serverConnection()->fd;
-    SSL *ssl = fd_table[fd].ssl;
-
-    // In the case the session is resuming, the certificates does not exist and
-    // we did not do any cert validation
-    if (resumingSession)
-        return true;
-
-    handleServerCertificate();
-
-    if (ConnStateData *csd = request->clientConnectionManager.valid()) {
-        if (Ssl::ServerBump *serverBump = csd->serverBump()) {
-            // 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 (Ssl::TheConfig.ssl_crt_validator && useCertValidator_) {
+        const int fd = serverConnection()->fd;
+        SSL *ssl = fd_table[fd].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
@@ -335,28 +197,29 @@ Ssl::PeerConnector::sslFinalized()
                    "validate that certificate.");
             // fall through to do blocking in-process generation.
             ErrorState *anErr = new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request.getRaw());
+
+            noteNegotiationDone(anErr);
             bail(anErr);
-            if (serverConnection()->getPeer()) {
-                peerConnectFailed(serverConnection()->getPeer());
-            }
             serverConn->close();
             return true;
         }
     }
+
+    noteNegotiationDone(NULL);
     return true;
 }
 
 void switchToTunnel(HttpRequest *request, Comm::ConnectionPointer & clientConn, Comm::ConnectionPointer &srvConn);
 
 void
-Ssl::PeerConnector::cbCheckForPeekAndSpliceDone(allow_t answer, void *data)
+Ssl::PeekingPeerConnector::cbCheckForPeekAndSpliceDone(allow_t answer, void *data)
 {
-    Ssl::PeerConnector *peerConnect = (Ssl::PeerConnector *) data;
+    Ssl::PeekingPeerConnector *peerConnect = (Ssl::PeekingPeerConnector *) data;
     peerConnect->checkForPeekAndSpliceDone((Ssl::BumpMode)answer.kind);
 }
 
 void
-Ssl::PeerConnector::checkForPeekAndSplice()
+Ssl::PeekingPeerConnector::checkForPeekAndSplice()
 {
     // Mark Step3 of bumping
     if (request->clientConnectionManager.valid()) {
@@ -370,11 +233,11 @@ Ssl::PeerConnector::checkForPeekAndSplice()
     ACLFilledChecklist *acl_checklist = new ACLFilledChecklist(
         ::Config.accessList.ssl_bump,
         request.getRaw(), NULL);
-    acl_checklist->nonBlockingCheck(Ssl::PeerConnector::cbCheckForPeekAndSpliceDone, this);
+    acl_checklist->nonBlockingCheck(Ssl::PeekingPeerConnector::cbCheckForPeekAndSpliceDone, this);
 }
 
 void
-Ssl::PeerConnector::checkForPeekAndSpliceDone(Ssl::BumpMode const action)
+Ssl::PeekingPeerConnector::checkForPeekAndSpliceDone(Ssl::BumpMode const action)
 {
     SSL *ssl = fd_table[serverConn->fd].ssl;
     BIO *b = SSL_get_rbio(ssl);
@@ -404,14 +267,15 @@ Ssl::PeerConnector::checkForPeekAndSpliceDone(Ssl::BumpMode const action)
         //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);
+        Ssl::PeerConnector::noteWantWrite();
     } else {
         splice = true;
         // Ssl Negotiation stops here. Last SSL checks for valid certificates
         // and if done, switch to tunnel mode
-        if (sslFinalized())
-            switchToTunnel(request.getRaw(), clientConn, serverConn);
+        if (sslFinalized()) {
+            debugs(83,5, "Abort NegotiateSSL on FD " << serverConn->fd << " and splice the connection");
+        }
     }
 }
 
@@ -440,40 +304,28 @@ Ssl::PeerConnector::sslCrtvdHandleReply(Ssl::CertValidationResponse const &valid
         validatorFailed = true;
 
     if (!errDetails && !validatorFailed) {
-        if (splice)
-            switchToTunnel(request.getRaw(), clientConn, serverConn);
-        else
-            callBack();
+        noteNegotiationDone(NULL);
+        callBack();
         return;
     }
 
+    if (errs) {
+        if (certErrors)
+            cbdataReferenceDone(certErrors);
+        certErrors = cbdataReference(errs);
+    }
+
     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*/
     }
 
+    noteNegotiationDone(anErr);
     bail(anErr);
-    if (serverConnection()->getPeer()) {
-        peerConnectFailed(serverConnection()->getPeer());
-    }
     serverConn->close();
     return;
 }
@@ -549,86 +401,70 @@ Ssl::PeerConnector::handleNegotiateError(const int ret)
     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:
-        setReadTimeout();
-        Comm::SetSelect(fd, COMM_SELECT_READ, &NegotiateSsl, this, 0);
+        noteWantRead();
         return;
 
     case SSL_ERROR_WANT_WRITE:
-        if ((srvBio->bumpMode() == Ssl::bumpPeek || srvBio->bumpMode() == Ssl::bumpStare) && srvBio->holdWrite()) {
-            debugs(81, DBG_IMPORTANT, "hold write on SSL connection on FD " << fd);
-            checkForPeekAndSplice();
-            return;
-        }
-        Comm::SetSelect(fd, COMM_SELECT_WRITE, &NegotiateSsl, this, 0);
+        noteWantWrite();
         return;
 
     case SSL_ERROR_SSL:
     case SSL_ERROR_SYSCALL:
         ssl_lib_error = ERR_get_error();
+        // proceed to the general error handling code
+        break;
+    default:
+        // no special error handling for all other errors
+        break;
+    }
+    noteSslNegotiationError(ret, ssl_error, ssl_lib_error);
+}
 
-        // In Peek mode, the ClientHello message sent to the server. If the
-        // server resuming a previous (spliced) SSL session with the client,
-        // then probably we are here because local SSL object does not know
-        // anything about the session being resumed.
-        //
-        if (srvBio->bumpMode() == Ssl::bumpPeek && (resumingSession = srvBio->resumingSession())) {
-            // we currently splice all resumed sessions unconditionally
-            if (const bool spliceResumed = true) {
-                checkForPeekAndSpliceDone(Ssl::bumpSplice);
-                return;
-            } // else fall through to find a matching ssl_bump action (with limited info)
-        }
-
-        // If we are in peek-and-splice mode and still we did not write to
-        // server yet, try to see if we should splice.
-        // In this case the connection can be saved.
-        // If the checklist decision is do not splice a new error will
-        // occure in the next SSL_connect call, and we will fail again.
-        // Abort on certificate validation errors to avoid splicing and
-        // thus hiding them.
-        // Abort if no certificate found probably because of malformed or
-        // unsupported server Hello message (TODO: make configurable).
-#if 1
-        if (!SSL_get_ex_data(ssl, ssl_ex_index_ssl_error_detail) &&
-                (srvBio->bumpMode() == Ssl::bumpPeek  || srvBio->bumpMode() == Ssl::bumpStare) && srvBio->holdWrite()) {
-            Ssl::X509_Pointer serverCert(SSL_get_peer_certificate(ssl));
-            if (serverCert.get()) {
-                debugs(81, 3, "Error ("  << ERR_error_string(ssl_lib_error, NULL) <<  ") but, hold write on SSL connection on FD " << fd);
-                checkForPeekAndSplice();
-                return;
-            }
-        }
-#endif
+void
+Ssl::PeerConnector::noteWantRead()
+{
+    setReadTimeout();
+    const int fd = serverConnection()->fd;
+    Comm::SetSelect(fd, COMM_SELECT_READ, &NegotiateSsl, this, 0);
+}
 
-        // 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;
+void
+Ssl::PeerConnector::noteWantWrite()
+{
+    const int fd = serverConnection()->fd;
+    Comm::SetSelect(fd, COMM_SELECT_WRITE, &NegotiateSsl, this, 0);
+    return;
+}
 
-        debugs(83, DBG_IMPORTANT, "Error negotiating SSL on FD " << fd <<
-               ": " << ERR_error_string(ssl_lib_error, NULL) << " (" <<
-               ssl_error << "/" << ret << "/" << errno << ")");
+void
+Ssl::PeerConnector::noteSslNegotiationError(const int ret, const int ssl_error, const int ssl_lib_error)
+{
+#ifdef EPROTO
+    int sysErrNo = EPROTO;
+#else
+    int sysErrNo = EACCES;
+#endif
 
-        break; // proceed to the general error handling code
+    // 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;
 
-    default:
-        break; // no special error handling for all other errors
-    }
+    const int fd = serverConnection()->fd;
+    debugs(83, DBG_IMPORTANT, "Error negotiating SSL on FD " << fd <<
+           ": " << ERR_error_string(ssl_lib_error, NULL) << " (" <<
+           ssl_error << "/" << ret << "/" << errno << ")");
 
-    ErrorState *const anErr = ErrorState::NewForwarding(ERR_SECURE_CONNECT_FAIL, request.getRaw());
+    ErrorState *anErr = NULL;
+    if (request != NULL)
+        anErr = ErrorState::NewForwarding(ERR_SECURE_CONNECT_FAIL, request.getRaw());
+    else
+        anErr = new ErrorState(ERR_SECURE_CONNECT_FAIL, Http::scServiceUnavailable, NULL);
     anErr->xerrno = sysErrNo;
 
+    SSL *ssl = fd_table[fd].ssl;
     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
@@ -645,30 +481,12 @@ Ssl::PeerConnector::handleNegotiateError(const int ret)
     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);
-                }
-            }
-        }
-    }
+    assert(certErrors == NULL);
+    // remember validation errors, if any
+    if (Ssl::CertErrors *errs = static_cast<Ssl::CertErrors*>(SSL_get_ex_data(ssl, ssl_ex_index_ssl_errors)))
+        certErrors = cbdataReference(errs);
 
+    noteNegotiationDone(anErr);
     bail(anErr);
 }
 
@@ -676,15 +494,6 @@ 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);
@@ -745,3 +554,281 @@ Ssl::PeerConnector::status() const
     return buf.content();
 }
 
+SSL_CTX *
+Ssl::BlindPeerConnector::getSslContext()
+{
+    if (const CachePeer *peer = serverConnection()->getPeer()) {
+        assert(peer->secure.encryptTransport);
+        SSL_CTX *sslContext = peer->sslContext;
+        return sslContext;
+    }
+    return NULL;
+}
+
+SSL *
+Ssl::BlindPeerConnector::initializeSsl()
+{
+    SSL *ssl = Ssl::PeerConnector::initializeSsl();
+    if (!ssl)
+        return NULL;
+
+    const CachePeer *peer = serverConnection()->getPeer();
+    assert(peer);
+
+    // NP: domain may be a raw-IP but it is now always set
+    assert(!peer->secure.sslDomain.isEmpty());
+
+    // const loss is okay here, ssl_ex_index_server is only read and not assigned a destructor
+    SBuf *host = new SBuf(peer->secure.sslDomain);
+    SSL_set_ex_data(ssl, ssl_ex_index_server, host);
+
+    if (peer->sslSession)
+        SSL_set_session(ssl, peer->sslSession);
+
+    return ssl;
+}
+
+void
+Ssl::BlindPeerConnector::noteNegotiationDone(ErrorState *error)
+{
+    if (error) {
+        // XXX: forward.cc calls peerConnectSucceeded() after an OK TCP connect but
+        // we call peerConnectFailed() if SSL failed afterwards. Is that OK?
+        // It is not clear whether we should call peerConnectSucceeded/Failed()
+        // based on TCP results, SSL results, or both. And the code is probably not
+        // consistent in this aspect across tunnelling and forwarding modules.
+        if (CachePeer *p = serverConnection()->getPeer())
+            peerConnectFailed(p);
+        return;
+    }
+
+    const int fd = serverConnection()->fd;
+    SSL *ssl = fd_table[fd].ssl;
+    if (serverConnection()->getPeer() && !SSL_session_reused(ssl)) {
+        if (serverConnection()->getPeer()->sslSession)
+            SSL_SESSION_free(serverConnection()->getPeer()->sslSession);
+
+        serverConnection()->getPeer()->sslSession = SSL_get1_session(ssl);
+    }
+}
+
+SSL_CTX *
+Ssl::PeekingPeerConnector::getSslContext()
+{
+    // XXX: locate a per-server context in Security:: instead
+    return ::Config.ssl_client.sslContext;
+}
+
+SSL *
+Ssl::PeekingPeerConnector::initializeSsl()
+{
+    SSL *ssl = Ssl::PeerConnector::initializeSsl();
+    if (!ssl)
+        return NULL;
+
+    if (ConnStateData *csd = request->clientConnectionManager.valid()) {
+
+        // client connection is required in the case we need to splice
+        // or terminate client and server connections
+        assert(clientConn != NULL);
+        SBuf *hostName = NULL;
+        Ssl::ClientBio *cltBio = NULL;
+
+        //Enable Status_request tls extension, required to bump some clients
+        SSL_set_tlsext_status_type(ssl, TLSEXT_STATUSTYPE_ocsp);
+
+        // In server-first bumping mode, clientSsl is NULL.
+        if (SSL *clientSsl = fd_table[clientConn->fd].ssl) {
+            BIO *b = SSL_get_rbio(clientSsl);
+            cltBio = static_cast<Ssl::ClientBio *>(b->ptr);
+            const Ssl::Bio::sslFeatures &features = cltBio->getFeatures();
+            if (!features.serverName.isEmpty())
+                hostName = new SBuf(features.serverName);
+        }
+
+        if (!hostName) {
+            // While we are peeking at the certificate, we may not know the server
+            // name that the client will request (after interception or CONNECT)
+            // unless it was the CONNECT request with a user-typed address.
+            const bool isConnectRequest = !csd->port->flags.isIntercepted();
+            if (!request->flags.sslPeek || isConnectRequest)
+                hostName = new SBuf(request->GetHost());
+        }
+
+        if (hostName)
+            SSL_set_ex_data(ssl, ssl_ex_index_server, (void*)hostName);
+
+        Must(!csd->serverBump() || csd->serverBump()->step <= Ssl::bumpStep2);
+        if (csd->sslBumpMode == Ssl::bumpPeek || csd->sslBumpMode == Ssl::bumpStare) {
+            assert(cltBio);
+            const Ssl::Bio::sslFeatures &features = cltBio->getFeatures();
+            if (features.sslVersion != -1) {
+                features.applyToSSL(ssl, csd->sslBumpMode);
+                // Should we allow it for all protocols?
+                if (features.sslVersion >= 3) {
+                    BIO *b = SSL_get_rbio(ssl);
+                    Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(b->ptr);
+                    // Inherite client features, like SSL version, SNI and other
+                    srvBio->setClientFeatures(features);
+                    srvBio->recordInput(true);
+                    srvBio->mode(csd->sslBumpMode);
+                }
+            }
+        } else {
+            // Set client SSL options
+            SSL_set_options(ssl, ::Security::ProxyOutgoingConfig.parsedOptions);
+
+            // Use SNI TLS extension only when we connect directly
+            // to the origin server and we know the server host name.
+            const char *sniServer = hostName ? hostName->c_str() :
+                                    (!request->GetHostIsNumeric() ? request->GetHost() : NULL);
+            if (sniServer)
+                Ssl::setClientSNI(ssl, sniServer);
+        }
+
+        // store peeked cert to check SQUID_X509_V_ERR_CERT_CHANGE
+        X509 *peeked_cert;
+        if (csd->serverBump() &&
+                (peeked_cert = csd->serverBump()->serverCert.get())) {
+            CRYPTO_add(&(peeked_cert->references),1,CRYPTO_LOCK_X509);
+            SSL_set_ex_data(ssl, ssl_ex_index_ssl_peeked_cert, peeked_cert);
+        }
+    }
+
+    return ssl;
+}
+
+void
+Ssl::PeekingPeerConnector::noteNegotiationDone(ErrorState *error)
+{
+    SSL *ssl = fd_table[serverConnection()->fd].ssl;
+
+    // Check the list error with
+    if (!request->clientConnectionManager.valid() || ! ssl)
+        return;
+
+    // remember the server certificate from the ErrorDetail object
+    if (Ssl::ServerBump *serverBump = request->clientConnectionManager->serverBump()) {
+        // remember validation errors, if any
+        if (certErrors) {
+            if (serverBump->sslErrors)
+                cbdataReferenceDone(serverBump->sslErrors);
+            serverBump->sslErrors = cbdataReference(certErrors);
+        }
+
+        if (!serverBump->serverCert.get()) {
+            // remember the server certificate from the ErrorDetail object
+            if (error && error->detail && error->detail->peerCert())
+                serverBump->serverCert.resetAndLock(error->detail->peerCert());
+            else {
+                handleServerCertificate();
+            }
+        }
+
+        if (error) {
+            // For intercepted connections, set the host name to the server
+            // certificate CN. Otherwise, we just hope that CONNECT is using
+            // a user-entered address (a host name or a user-entered IP).
+            const bool isConnectRequest = !request->clientConnectionManager->port->flags.isIntercepted();
+            if (request->flags.sslPeek && !isConnectRequest) {
+                if (X509 *srvX509 = serverBump->serverCert.get()) {
+                    if (const char *name = Ssl::CommonHostName(srvX509)) {
+                        request->SetHost(name);
+                        debugs(83, 3, "reset request host: " << name);
+                    }
+                }
+            }
+        }
+    }
+
+    if (!error && splice)
+        switchToTunnel(request.getRaw(), clientConn, serverConn);
+}
+
+void
+Ssl::PeekingPeerConnector::noteWantWrite()
+{
+    const int fd = serverConnection()->fd;
+    SSL *ssl = fd_table[fd].ssl;
+    BIO *b = SSL_get_rbio(ssl);
+    Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(b->ptr);
+
+    if ((srvBio->bumpMode() == Ssl::bumpPeek || srvBio->bumpMode() == Ssl::bumpStare) && srvBio->holdWrite()) {
+        debugs(81, DBG_IMPORTANT, "hold write on SSL connection on FD " << fd);
+        checkForPeekAndSplice();
+        return;
+    }
+
+    Ssl::PeerConnector::noteWantWrite();
+}
+
+void
+Ssl::PeekingPeerConnector::noteSslNegotiationError(const int result, const int ssl_error, const int ssl_lib_error)
+{
+    const int fd = serverConnection()->fd;
+    SSL *ssl = fd_table[fd].ssl;
+    BIO *b = SSL_get_rbio(ssl);
+    Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(b->ptr);
+
+    // In Peek mode, the ClientHello message sent to the server. If the
+    // server resuming a previous (spliced) SSL session with the client,
+    // then probably we are here because local SSL object does not know
+    // anything about the session being resumed.
+    //
+    if (srvBio->bumpMode() == Ssl::bumpPeek && (resumingSession = srvBio->resumingSession())) {
+        // we currently splice all resumed sessions unconditionally
+        if (const bool spliceResumed = true) {
+            bypassCertValidator();
+            checkForPeekAndSpliceDone(Ssl::bumpSplice);
+            return;
+        } // else fall through to find a matching ssl_bump action (with limited info)
+    }
+
+    // If we are in peek-and-splice mode and still we did not write to
+    // server yet, try to see if we should splice.
+    // In this case the connection can be saved.
+    // If the checklist decision is do not splice a new error will
+    // occur in the next SSL_connect call, and we will fail again.
+    // Abort on certificate validation errors to avoid splicing and
+    // thus hiding them.
+    // Abort if no certificate found probably because of malformed or
+    // unsupported server Hello message (TODO: make configurable).
+    if (!SSL_get_ex_data(ssl, ssl_ex_index_ssl_error_detail) &&
+            (srvBio->bumpMode() == Ssl::bumpPeek  || srvBio->bumpMode() == Ssl::bumpStare) && srvBio->holdWrite()) {
+        Ssl::X509_Pointer serverCert(SSL_get_peer_certificate(ssl));
+        if (serverCert.get()) {
+            debugs(81, 3, "Error ("  << ERR_error_string(ssl_lib_error, NULL) <<  ") but, hold write on SSL connection on FD " << fd);
+            checkForPeekAndSplice();
+            return;
+        }
+    }
+
+    // else call parent noteNegotiationError to produce an error page
+    Ssl::PeerConnector::noteSslNegotiationError(result, ssl_error, ssl_lib_error);
+}
+
+void
+Ssl::PeekingPeerConnector::handleServerCertificate()
+{
+    if (serverCertificateHandled)
+        return;
+
+    if (ConnStateData *csd = request->clientConnectionManager.valid()) {
+        const int fd = serverConnection()->fd;
+        SSL *ssl = fd_table[fd].ssl;
+        Ssl::X509_Pointer serverCert(SSL_get_peer_certificate(ssl));
+        if (!serverCert.get())
+            return;
+
+        serverCertificateHandled = true;
+
+        csd->resetSslCommonName(Ssl::CommonHostName(serverCert.get()));
+        debugs(83, 5, "HTTPS server CN: " << csd->sslCommonName() <<
+               " bumped: " << *serverConnection());
+
+        // remember the server certificate for later use
+        if (Ssl::ServerBump *serverBump = csd->serverBump()) {
+            serverBump->serverCert.reset(serverCert.release());
+        }
+    }
+}
index 75b76855ebd0e1a860191ff44fba1e6dbf851388..1dee04c6ca81822e5efcc5a09a074aac7089edf8 100644 (file)
@@ -27,10 +27,9 @@ class CertValidationResponse;
 
 /**
  \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.
+ * Connects Squid to SSL/TLS-capable peers or services.
+ * Contains common code and interfaces of various specialized PeerConnectors,
+ * including peer certificate validation code.
  \par
  * The caller receives a call back with Security::EncryptorAnswer. If answer.error
  * is not nil, then there was an error and the SSL connection to the SSL peer
@@ -54,7 +53,7 @@ class CertValidationResponse;
  \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
 {
     CBDATA_CLASS(PeerConnector);
@@ -72,9 +71,7 @@ public:
     typedef RefCount<HttpRequest> HttpRequestPointer;
 
 public:
-    PeerConnector(HttpRequestPointer &aRequest,
-                  const Comm::ConnectionPointer &aServerConn,
-                  const Comm::ConnectionPointer &aClientConn,
+    PeerConnector(const Comm::ConnectionPointer &aServerConn,
                   AsyncCall::Pointer &aCallback, const time_t timeout = 0);
     virtual ~PeerConnector();
 
@@ -100,7 +97,7 @@ protected:
     /// silent server
     void setReadTimeout();
 
-    void initializeSsl(); ///< Initializes SSL state
+    virtual SSL *initializeSsl(); ///< Initializes SSL state
 
     /// Performs a single secure connection negotiation step.
     /// It is called multiple times untill the negotiation finish or aborted.
@@ -111,29 +108,53 @@ protected:
     /// Otherwise, returns true, regardless of negotiation success/failure.
     bool sslFinalized();
 
-    /// Initiates the ssl_bump acl check in step3 SSL bump step to decide
-    /// about bumping, splicing or terminating the connection.
-    void checkForPeekAndSplice();
-
-    /// Callback function for ssl_bump acl check in step3  SSL bump step.
-    /// Handles the final bumping decision.
-    void checkForPeekAndSpliceDone(Ssl::BumpMode const);
-
     /// 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
+    /// Called when the openSSL SSL_connect fnction request more data from
+    /// the remote SSL server. Sets the read timeout and sets the
+    /// Squid COMM_SELECT_READ handler.
+    void noteWantRead();
+
+    /// Called when the openSSL SSL_connect function needs to write data to
+    /// the remote SSL server. Sets the Squid COMM_SELECT_WRITE handler.
+    virtual void noteWantWrite();
+
+    /// Called when the SSL_connect function aborts with an SSL negotiation error
+    /// \param result the SSL_connect return code
+    /// \param ssl_error the error code returned from the SSL_get_error function
+    /// \param ssl_lib_error the error returned from the ERR_Get_Error function
+    virtual void noteSslNegotiationError(const int result, const int ssl_error, const int ssl_lib_error);
+
+    /// Called when the SSL negotiation to the server completed and the certificates
+    /// validated using the cert validator.
+    /// \param error if not NULL the SSL negotiation was aborted with an error
+    virtual void noteNegotiationDone(ErrorState *error) {}
+
+    /// Must implemented by the kid classes to return the SSL_CTX object to use
+    /// for building the SSL objects.
+    virtual SSL_CTX *getSslContext() = 0;
 
     /// 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
 
+    /// If called the certificates validator will not used
+    void bypassCertValidator() {useCertValidator_ = false;}
+
+    HttpRequestPointer request; ///< peer connection trigger or cause
+    Comm::ConnectionPointer serverConn; ///< TCP connection to the peer
+    /// Certificate errors found from SSL validation procedure or from cert
+    /// validator
+    Ssl::CertErrors *certErrors;
+private:
+    PeerConnector(const PeerConnector &); // not implemented
+    PeerConnector &operator =(const PeerConnector &); // not implemented
+
     /// Callback the caller class, and pass the ready to communicate secure
     /// connection or an error if PeerConnector failed.
     void callBack();
@@ -144,27 +165,79 @@ private:
     /// Check SSL errors returned from cert validator against sslproxy_cert_error access list
     Ssl::CertErrors *sslCrtvdCheckForErrors(Ssl::CertValidationResponse const &, Ssl::ErrorDetail *&);
 
-    /// Updates associated client connection manager members
-    /// if the server certificate was received from the server.
-    void handleServerCertificate();
-
     /// 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);
+    AsyncCall::Pointer callback; ///< we call this with the results
+    AsyncCall::Pointer closeHandler; ///< we call this when the connection closed
+    time_t negotiationTimeout; ///< the SSL connection timeout to use
+    time_t startTime; ///< when the peer connector negotiation started
+    bool useCertValidator_; ///< whether the certificate validator should bypassed
+};
+
+/// A simple PeerConnector for SSL/TLS cache_peers. No SslBump capabilities.
+class BlindPeerConnector: public PeerConnector {
+    CBDATA_CLASS(BlindPeerConnector);
+public:
+    BlindPeerConnector(HttpRequestPointer &aRequest,
+                       const Comm::ConnectionPointer &aServerConn,
+                       AsyncCall::Pointer &aCallback, const time_t timeout = 0): AsyncJob("Ssl::BlindPeerConnector"),
+        PeerConnector(aServerConn, aCallback, timeout) { request = aRequest; }
+
+    /* PeerConnector API */
+
+    /// Calls parent initializeSSL, configure the created SSL object to try reuse SSL session
+    /// and sets the hostname to use for certificates validation
+    virtual SSL *initializeSsl();
+
+    /// Return the configured SSL_CTX object
+    virtual SSL_CTX *getSslContext();
+
+    /// On error calls peerConnectFailed function, on success store the used SSL session
+    /// for later use
+    virtual void noteNegotiationDone(ErrorState *error);
+};
+
+/// A PeerConnector for HTTP origin servers. Capable of SslBumping.
+class PeekingPeerConnector: public PeerConnector {
+    CBDATA_CLASS(PeekingPeerConnector);
+public:
+    PeekingPeerConnector(HttpRequestPointer &aRequest,
+                         const Comm::ConnectionPointer &aServerConn,
+                         const Comm::ConnectionPointer &aClientConn,
+                         AsyncCall::Pointer &aCallback, const time_t timeout = 0): AsyncJob("Ssl::PeekingPeerConnector"),
+        PeerConnector(aServerConn, aCallback, timeout), clientConn(aClientConn), splice(false), resumingSession(false), serverCertificateHandled(false) { request = aRequest; }
+
+    /* PeerConnector API */
+    virtual SSL *initializeSsl();
+    virtual SSL_CTX *getSslContext();
+    virtual void noteWantWrite();
+    virtual void noteSslNegotiationError(const int result, const int ssl_error, const int ssl_lib_error);
+    virtual void noteNegotiationDone(ErrorState *error);
+
+    /// Updates associated client connection manager members
+    /// if the server certificate was received from the server.
+    void handleServerCertificate();
+
+    /// Initiates the ssl_bump acl check in step3 SSL bump step to decide
+    /// about bumping, splicing or terminating the connection.
+    void checkForPeekAndSplice();
+
+    /// Callback function for ssl_bump acl check in step3  SSL bump step.
+    /// Handles the final bumping decision.
+    void checkForPeekAndSpliceDone(Ssl::BumpMode const);
 
     /// A wrapper function for checkForPeekAndSpliceDone for use with acl
     static void cbCheckForPeekAndSpliceDone(allow_t answer, void *data);
 
-    HttpRequestPointer request; ///< peer connection trigger or cause
-    Comm::ConnectionPointer serverConn; ///< TCP connection to the peer
+private:
     Comm::ConnectionPointer clientConn; ///< TCP connection to the client
     AsyncCall::Pointer callback; ///< we call this with the results
     AsyncCall::Pointer closeHandler; ///< we call this when the connection closed
-    time_t negotiationTimeout; ///< the ssl connection timeout to use
     time_t startTime; ///< when the peer connector negotiation started
-    bool splice; ///< Whether we are going to splice or not
+    bool splice; ///< whether we are going to splice or not
     bool resumingSession; ///< whether it is an SSL resuming session connection
     bool serverCertificateHandled; ///< whether handleServerCertificate() succeeded
 };
index 5b279f06acf5cf38286c13b4314eb7ab32023a9b..f16fe6245f876c93c4eb62c5c4ef70cfd1e384c3 100644 (file)
@@ -1022,8 +1022,8 @@ TunnelStateData::connectToPeer()
             AsyncCall::Pointer callback = asyncCall(5,4,
                                                     "TunnelStateData::ConnectedToPeer",
                                                     MyAnswerDialer(&TunnelStateData::connectedToPeer, this));
-            Ssl::PeerConnector *connector =
-                new Ssl::PeerConnector(request, server.conn, client.conn, callback);
+            Ssl::BlindPeerConnector *connector =
+                new Ssl::BlindPeerConnector(request, server.conn, callback);
             AsyncJob::Start(connector); // will call our callback
             return;
         }