#endif
PROTO_URN,
PROTO_WHOIS,
- PROTO_INTERNAL,
+ PROTO_INTERNAL, // miss on an internal object such as an icon
PROTO_ICY,
+#if USE_SSL
+ PROTO_SSL_PEEK, // an internal request to peek at an HTTPS server
+#endif
PROTO_UNKNOWN,
PROTO_MAX
} ProtocolType;
#if USE_SSL
#include "ssl/context_storage.h"
#include "ssl/helper.h"
+#include "ssl/ServerPeeker.h"
#include "ssl/support.h"
#include "ssl/gadgets.h"
#endif
else {
char buf[MAX_IPSTRLEN];
debugs(33, 4, HERE << details << " try to generate a Dynamic SSL CTX");
- connState->switchToHttps(details->local.NtoA(buf, sizeof(buf)));
+ connState->switchToHttps(details->local.NtoA(buf, sizeof(buf)), details->local.GetPort());
}
}
// using tproxy-provided destination IP and port.
HttpRequest *request = new HttpRequest();
static char ip[MAX_IPSTRLEN];
- assert(params.conn->flags & COMM_TRANSPARENT);
+ assert(params.conn->flags & (COMM_TRANSPARENT | COMM_INTERCEPTION));
request->SetHost(params.conn->local.NtoA(ip, sizeof(ip)));
request->port = params.conn->local.GetPort();
request->myportname = s->name;
}
void
-ConnStateData::switchToHttps(const char *host)
+ConnStateData::switchToHttps(const char *host, const int port)
{
assert(!switchedToHttps_);
freeAllContexts();
//currentobject->connIsFinished();
+ /* careful: freeAllContexts() above frees request, host, etc. */
+
// We are going to read new request
flags.readMore = true;
debugs(33, 5, HERE << "converting " << clientConnection << " to SSL");
+ const bool alwaysBumpServerFirst = true;
+ if (alwaysBumpServerFirst) {
+ Must(!httpsPeeker.set());
+ httpsPeeker = AsyncJob::Start(new Ssl::ServerPeeker(
+ this, sslHostName.termedBuf(), port));
+ // will call httpsPeeked() with certificate and connection, eventually
+ return;
+ }
+
+ // otherwise, use sslHostName
+ getSslContextStart();
+}
+
+void
+ConnStateData::httpsPeeked(Comm::ConnectionPointer serverConnection)
+{
+ Must(httpsPeeker.set());
+ // XXX: handle httpsPeeker errors
+
+ pinConnection(serverConnection, NULL, NULL, false);
+
+ // XXX: change sslHostName based on httpsPeeker results
+ debugs(33, 5, HERE << "bumped HTTPS server: " << sslHostName);
+ httpsPeeker.clear();
getSslContextStart();
}
continue;
}
+ // TODO: merge with similar code in clientHttpConnectionsOpen()
+ if (s->sslBump && !Config.accessList.ssl_bump) {
+ debugs(33, DBG_IMPORTANT, "WARNING: No ssl_bump configured. Disabling ssl-bump on " << s->protocol << "_port " << s->http.s);
+ s->sslBump = 0;
+ }
+
+ if (s->sslBump && !s->staticSslContext && !s->generateHostCertificates) {
+ debugs(1, DBG_IMPORTANT, "Will not bump SSL at http_port " << s->http.s << " due to SSL initialization failure.");
+ s->sslBump = 0;
+ }
+
+ if (s->sslBump) {
+ // Create ssl_ctx cache for this port.
+ Ssl::TheGlobalContextStorage.addLocalStorage(s->s, s->dynamicCertMemCacheSize == std::numeric_limits<size_t>::max() ? 4194304 : s->dynamicCertMemCacheSize);
+ }
+
// Fill out a Comm::Connection which IPC will open as a listener for us
s->http.listenConn = new Comm::Connection;
s->http.listenConn->local = s->http.s;
clientConnection->close();
}
-/* This is a comm call normally scheduled by comm_close() */
+/// Our close handler called by Comm when the pinned connection is closed
void
ConnStateData::clientPinnedConnectionClosed(const CommCloseCbParams &io)
{
+ pinning.closeHandler = NULL; // Comm unregisters handlers before calling
unpinConnection();
}
if (Comm::IsConnOpen(pinning.serverConnection)) {
if (pinning.serverConnection->fd == pinServer->fd)
return;
+ }
- unpinConnection(); // clears fields ready for re-use. Prevent close() scheduling our close handler.
- pinning.serverConnection->close();
- } else
- unpinConnection(); // clears fields ready for re-use.
+ unpinConnection(); // closes pinned connection, if any, and resets fields.
pinning.serverConnection = pinServer;
- pinning.host = xstrdup(request->GetHost());
- pinning.port = request->port;
+
+ // when pinning an SSL bumped connection, the request may be NULL
+ const char *pinnedHost = "[unknown]";
+ if (request) {
+ pinning.host = xstrdup(request->GetHost());
+ pinning.port = request->port;
+ pinnedHost = pinning.host;
+ } else {
+ pinning.port = pinServer->remote.GetPort();
+ }
pinning.pinned = true;
if (aPeer)
pinning.peer = cbdataReference(aPeer);
pinning.auth = auth;
char stmp[MAX_IPSTRLEN];
snprintf(desc, FD_DESC_SZ, "%s pinned connection for %s (%d)",
- (auth || !aPeer) ? request->GetHost() : aPeer->name, clientConnection->remote.ToURL(stmp,MAX_IPSTRLEN), clientConnection->fd);
+ (auth || !aPeer) ? pinnedHost : aPeer->name,
+ clientConnection->remote.ToURL(stmp,MAX_IPSTRLEN),
+ clientConnection->fd);
fd_note(pinning.serverConnection->fd, desc);
typedef CommCbMemFunT<ConnStateData, CommCloseCbParams> Dialer;
if (pinning.peer)
cbdataReferenceDone(pinning.peer);
+ if (Comm::IsConnOpen(pinning.serverConnection)) {
if (pinning.closeHandler != NULL) {
comm_remove_close_handler(pinning.serverConnection->fd, pinning.closeHandler);
pinning.closeHandler = NULL;
}
/// also close the server side socket, we should not use it for any future requests...
+ // TODO: do not close if called from our close handler?
pinning.serverConnection->close();
+ }
+
safe_free(pinning.host);
/* NOTE: pinning.pinned should be kept. This combined with fd == -1 at the end of a request indicates that the host
virtual void swanSong();
#if USE_SSL
+ /// called by Ssl::ServerPeeker when it is done bumping the server
+ void httpsPeeked(Comm::ConnectionPointer serverConnection);
+
/// Start to create dynamic SSL_CTX for host or uses static port SSL context.
void getSslContextStart();
/**
/// Proccess response from ssl_crtd.
void sslCrtdHandleReply(const char * reply);
- void switchToHttps(const char *host);
+ void switchToHttps(const char *host, const int port);
bool switchedToHttps() const { return switchedToHttps_; }
#else
bool switchedToHttps() const { return false; }
CBDATA_CLASS2(ConnStateData);
bool closing_;
+#if USE_SSL
bool switchedToHttps_;
String sslHostName; ///< Host name for SSL certificate generation
+
+ /// a job that connects to the HTTPS server to get its SSL certificate
+ AsyncJob::Pointer httpsPeeker;
+#endif
+
AsyncCall::Pointer reader; ///< set when we are reading
BodyPipe::Pointer bodyPipe; // set when we are reading request body
};
return;
}
- getConn()->switchToHttps(request->GetHost());
+ getConn()->switchToHttps(request->GetHost(), request->port);
}
void
// To resolve this we must force DIRECT and only to the original client destination.
if (Config.onoff.client_dst_passthru && request && !request->flags.redirected &&
(request->flags.intercepted || request->flags.spoof_client_ip)) {
- Comm::ConnectionPointer p = new Comm::Connection();
- p->remote = clientConn->local;
- p->peerType = ORIGINAL_DST;
- getOutgoingAddress(request, p);
- serverDestinations.push_back(p);
+ selectPeerForIntercepted();
// destination "found". continue with the forwarding.
startConnectionOrFail();
}
}
+/// bypasses peerSelect() when dealing with intercepted requests
+void
+FwdState::selectPeerForIntercepted()
+{
+ // use pinned connection if available
+ Comm::ConnectionPointer p;
+ if (ConnStateData *client = request->pinnedConnection())
+ p = client->validatePinnedConnection(request, NULL);
+
+ if (p != NULL && Comm::IsConnOpen(p)) {
+ debugs(17, 3, HERE << "reusing a pinned conn: " << *p);
+ /* duplicate peerSelectPinned() effects */
+ p->peerType = PINNED;
+ entry->ping_status = PING_DONE; /* Skip ICP */
+ } else {
+ debugs(17, 3, HERE << "opening a new conn: " << *p);
+ p = new Comm::Connection();
+ p->peerType = ORIGINAL_DST;
+ p->remote = clientConn->local;
+ getOutgoingAddress(request, p);
+ }
+
+ serverDestinations.push_back(p);
+}
+
void
FwdState::completed()
{
#if USE_SSL
if ((serverConnection()->getPeer() && serverConnection()->getPeer()->use_ssl) ||
- (!serverConnection()->getPeer() && request->protocol == AnyP::PROTO_HTTPS)) {
+ (!serverConnection()->getPeer() &&
+ (request->protocol == AnyP::PROTO_HTTPS || request->protocol == AnyP::PROTO_SSL_PEEK))) {
initiateSSL();
return;
}
}
#endif
+#if USE_SSL
+ if (request->protocol == AnyP::PROTO_SSL_PEEK) {
+ CallJobHere1(17, 4, request->clientConnectionManager, ConnStateData,
+ ConnStateData::httpsPeeked, serverConnection());
+ unregister(serverConn); // async call owns it now
+ complete(); // destroys us
+ return;
+ }
+#endif
+
if (serverConnection()->getPeer() != NULL) {
serverConnection()->getPeer()->stats.fetches++;
request->peer_login = serverConnection()->getPeer()->login;
case AnyP::PROTO_INTERNAL:
+#if USE_SSL
+ case AnyP::PROTO_SSL_PEEK:
+#endif
+
case AnyP::PROTO_URN:
fatal_dump("Should never get here");
break;
FwdState(const Comm::ConnectionPointer &client, StoreEntry *, HttpRequest *);
void start(Pointer aSelf);
+ void selectPeerForIntercepted();
static void logReplyStatus(int tries, http_status status);
void doneWithRetries();
void completed();
ErrorDetail.h \
ErrorDetailManager.cc \
ErrorDetailManager.h \
+ ServerPeeker.cc \
+ ServerPeeker.h \
support.cc \
support.h
--- /dev/null
+/*
+ * $Id$
+ *
+ * DEBUG: section 33 Client-side Routines
+ *
+ */
+
+#include "config.h"
+
+#include "client_side.h"
+#include "forward.h"
+#include "ssl/ServerPeeker.h"
+#include "Store.h"
+
+
+CBDATA_NAMESPACED_CLASS_INIT(Ssl, ServerPeeker);
+
+
+Ssl::ServerPeeker::ServerPeeker(ConnStateData *anInitiator,
+ const char *host, const int port):
+ AsyncJob("Ssl::ServerPeeker"),
+ initiator(anInitiator),
+ clientConnection(anInitiator->clientConnection),
+ request(new HttpRequest),
+ entry(NULL)
+{
+ debugs(33, 4, HERE << "will peek at " << host << ':' << port);
+
+ request->SetHost(host);
+ request->port = port;
+ request->protocol = AnyP::PROTO_SSL_PEEK;
+ request->clientConnectionManager = initiator;
+}
+
+void
+Ssl::ServerPeeker::start()
+{
+ const char *uri = urlCanonical(request);
+ entry = storeCreateEntry(uri, uri, request->flags, request->method);
+
+ FwdState::fwdStart(clientConnection, entry, request);
+
+ // XXX: wait for FwdState to tell us the connection is ready
+
+ // TODO: send our answer to the initiator
+ // CallJobHere(33, 4, initiator, ConnStateData, ConnStateData::httpsPeeked);
+ initiator.clear(); // will trigger the end of the job
+}
+
+bool
+Ssl::ServerPeeker::doneAll() const
+{
+ return !initiator.valid() && AsyncJob::doneAll();
+}
+
+void
+Ssl::ServerPeeker::swanSong()
+{
+}
--- /dev/null
+#ifndef _SQUID_SSL_PEEKER_H
+#define _SQUID_SSL_PEEKER_H
+
+#include "base/AsyncJob.h"
+#include "base/CbcPointer.h"
+#include "comm/forward.h"
+#include "HttpRequest.h"
+#include "ip/Address.h"
+
+class ConnStateData;
+
+namespace Ssl
+{
+
+/**
+ \ingroup ServerProtocolSSLAPI
+ * A job to facilitate connecting to the HTTPS server to learn its certificate.
+ *
+ * The Peeker job calls FwdState::fwdStart(). There are two possible outcomes:
+ *
+ * Success: FwdState calls ConnStateData which pins the establihsed connection
+ * for future bumped HTTP requests (TODO: and stops this job).
+ *
+ * Error: FwdState Stores the error (TODO: and this job preserves it for
+ * for serving to the client in response to the first bumped request).
+ */
+class ServerPeeker: public AsyncJob
+{
+public:
+ typedef CbcPointer<ServerPeeker> Pointer;
+
+ explicit ServerPeeker(ConnStateData *anInitiator, const char *host, const int port);
+
+ /* AsyncJob API */
+ //virtual ~ServerPeeker();
+ virtual void start();
+ virtual bool doneAll() const;
+ virtual void swanSong();
+
+private:
+ /// connection manager waiting for peeked server info
+ CbcPointer<ConnStateData> initiator;
+
+ /// client-Squid connection which triggered this job
+ Comm::ConnectionPointer clientConnection;
+
+ /// faked, minimal request; required by server-side API
+ HttpRequest::Pointer request;
+
+ StoreEntry *entry; ///< for receiving Squid-generated error messages
+
+ CBDATA_CLASS2(ServerPeeker);
+};
+
+} // namespace Ssl
+
+#endif