#include "CachePeer.h"
#include "ChunkedCodingParser.h"
#include "client_db.h"
+#include "client_side.h"
#include "client_side_reply.h"
#include "client_side_request.h"
-#include "client_side.h"
#include "ClientRequestContext.h"
#include "clientStream.h"
#include "comm.h"
#include "ClientInfo.h"
#endif
#if USE_SSL
-#include "ssl/ProxyCerts.h"
#include "ssl/context_storage.h"
+#include "ssl/gadgets.h"
#include "ssl/helper.h"
+#include "ssl/ProxyCerts.h"
#include "ssl/ServerBump.h"
#include "ssl/support.h"
-#include "ssl/gadgets.h"
#endif
#if USE_SSL_CRTD
-#include "ssl/crtd_message.h"
#include "ssl/certificate_db.h"
+#include "ssl/crtd_message.h"
#endif
#if HAVE_LIMITS_H
CBDATA_CLASS_INIT(ClientSocketContext);
-void *
-ClientSocketContext::operator new (size_t byteCount)
-{
- /* derived classes with different sizes must implement their own new */
- assert (byteCount == sizeof (ClientSocketContext));
- CBDATA_INIT_TYPE(ClientSocketContext);
- return cbdataAlloc(ClientSocketContext);
-}
-
-void
-ClientSocketContext::operator delete (void *address)
-{
- cbdataFree (address);
-}
-
/* Local functions */
/* ClientSocketContext */
static ClientSocketContext *ClientSocketContextNew(const Comm::ConnectionPointer &clientConn, ClientHttpRequest *);
char *skipLeadingSpace(char *aString);
static void connNoteUseOfBuffer(ConnStateData* conn, size_t byteCount);
-static ConnStateData *connStateCreate(const Comm::ConnectionPointer &client, AnyP::PortCfg *port);
-
clientStreamNode *
ClientSocketContext::getTail() const
{
}
}
- ACLFilledChecklist *checklist = clientAclChecklistCreate(Config.accessList.log, this);
-
+ ACLFilledChecklist checklist(NULL, request, NULL);
if (al->reply) {
- checklist->reply = al->reply;
- HTTPMSGLOCK(checklist->reply);
+ checklist.reply = al->reply;
+ HTTPMSGLOCK(checklist.reply);
+ }
+
+ if (request) {
+ al->adapted_request = request;
+ HTTPMSGLOCK(al->adapted_request);
}
+ accessLogLog(al, &checklist);
- if (!Config.accessList.log || checklist->fastCheck() == ACCESS_ALLOWED) {
- if (request) {
- al->adapted_request = request;
- HTTPMSGLOCK(al->adapted_request);
+ bool updatePerformanceCounters = true;
+ if (Config.accessList.stats_collection) {
+ ACLFilledChecklist statsCheck(Config.accessList.stats_collection, request, NULL);
+ if (al->reply) {
+ statsCheck.reply = al->reply;
+ HTTPMSGLOCK(statsCheck.reply);
}
- accessLogLog(al, checklist);
+ updatePerformanceCounters = (statsCheck.fastCheck() == ACCESS_ALLOWED);
+ }
+
+ if (updatePerformanceCounters) {
if (request)
updateCounters();
if (getConn() != NULL && getConn()->clientConnection != NULL)
clientdbUpdate(getConn()->clientConnection->remote, logType, AnyP::PROTO_HTTP, out.size);
}
-
- delete checklist;
}
void
if (Comm::IsConnOpen(clientConnection))
clientConnection->close();
- clientConnection = NULL;
#if USE_AUTH
// NP: do this bit after closing the connections to avoid side effects from unwanted TCP RST
strlen(host);
http->uri = (char *)xcalloc(url_sz, 1);
const char *protocol = switchedToHttps ?
- "https" : conn->port->protocol;
+ "https" : URLScheme(conn->port->transport.protocol).const_str();
snprintf(http->uri, url_sz, "%s://%s%s", protocol, host, url);
debugs(33, 5, "ACCEL VHOST REWRITE: '" << http->uri << "'");
} else if (conn->port->defaultsite /* && !vhost */) {
snprintf(vportStr, sizeof(vportStr),":%d",vport);
}
snprintf(http->uri, url_sz, "%s://%s%s%s",
- conn->port->protocol, conn->port->defaultsite, vportStr, url);
+ URLScheme(conn->port->transport.protocol).const_str(), conn->port->defaultsite, vportStr, url);
debugs(33, 5, "ACCEL DEFAULTSITE REWRITE: '" << http->uri <<"'");
} else if (vport > 0 /* && (!vhost || no Host:) */) {
debugs(33, 5, "ACCEL VPORT REWRITE: http_port IP + vport=" << vport);
http->uri = (char *)xcalloc(url_sz, 1);
http->getConn()->clientConnection->local.toHostStr(ipbuf,MAX_IPSTRLEN);
snprintf(http->uri, url_sz, "%s://%s:%d%s",
- http->getConn()->port->protocol,
+ URLScheme(conn->port->transport.protocol).const_str(),
ipbuf, vport, url);
debugs(33, 5, "ACCEL VPORT REWRITE: '" << http->uri << "'");
}
int url_sz = strlen(url) + 32 + Config.appendDomainLen +
strlen(host);
http->uri = (char *)xcalloc(url_sz, 1);
- snprintf(http->uri, url_sz, "%s://%s%s", conn->port->protocol, host, url);
+ snprintf(http->uri, url_sz, "%s://%s%s", URLScheme(conn->port->transport.protocol).const_str(), host, url);
debugs(33, 5, "TRANSPARENT HOST REWRITE: '" << http->uri <<"'");
} else {
/* Put the local socket IP address as the hostname. */
http->uri = (char *)xcalloc(url_sz, 1);
http->getConn()->clientConnection->local.toHostStr(ipbuf,MAX_IPSTRLEN);
snprintf(http->uri, url_sz, "%s://%s:%d%s",
- http->getConn()->port->protocol,
+ URLScheme(http->getConn()->port->transport.protocol).const_str(),
ipbuf, http->getConn()->clientConnection->local.port(), url);
debugs(33, 5, "TRANSPARENT REWRITE: '" << http->uri << "'");
}
/* deny CONNECT via accelerated ports */
if (*method_p == Http::METHOD_CONNECT && csd->port && csd->port->flags.accelSurrogate) {
- debugs(33, DBG_IMPORTANT, "WARNING: CONNECT method received on " << csd->port->protocol << " Accelerator port " << csd->port->s.port() );
+ debugs(33, DBG_IMPORTANT, "WARNING: CONNECT method received on " << csd->port->transport.protocol << " Accelerator port " << csd->port->s.port());
/* XXX need a way to say "this many character length string" */
debugs(33, DBG_IMPORTANT, "WARNING: for request: " << hp->buf);
hp->request_parse_status = Http::scMethodNotAllowed;
goto finish;
}
- /* RFC 2616 section 10.5.6 : handle unsupported HTTP versions cleanly. */
- /* We currently only accept 0.9, 1.0, 1.1 */
+ /* RFC 2616 section 10.5.6 : handle unsupported HTTP major versions cleanly. */
+ /* We currently only support 0.9, 1.0, 1.1 properly */
if ( (http_ver.major == 0 && http_ver.minor != 9) ||
- (http_ver.major == 1 && http_ver.minor > 1 ) ||
(http_ver.major > 1) ) {
clientStreamNode *node = context->getClientReplyContext();
void
ConnStateData::requestTimeout(const CommTimeoutCbParams &io)
{
-#if THIS_CONFUSES_PERSISTENT_CONNECTION_AWARE_BROWSERS_AND_USERS
- debugs(33, 3, "requestTimeout: FD " << io.fd << ": lifetime is expired.");
-
- if (COMMIO_FD_WRITECB(io.fd)->active) {
- /* FIXME: If this code is reinstated, check the conn counters,
- * not the fd table state
- */
- /*
- * Some data has been sent to the client, just close the FD
- */
- clientConnection->close();
- } else if (nrequests) {
- /*
- * assume its a persistent connection; just close it
- */
- clientConnection->close();
- } else {
- /*
- * Generate an error
- */
- ClientHttpRequest **H;
- clientStreamNode *node;
- ClientHttpRequest *http = parseHttpRequestAbort(this, "error:Connection%20lifetime%20expired");
- node = http->client_stream.tail->prev->data;
- clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
- assert (repContext);
- repContext->setReplyToError(ERR_LIFETIME_EXP,
- Http::scRequestTimeout, Http::METHOD_NONE, "N/A", &CachePeer.sin_addr,
- NULL, NULL, NULL);
- /* No requests can be outstanded */
- assert(chr == NULL);
- /* add to the client request queue */
-
- for (H = &chr; *H; H = &(*H)->next);
- *H = http;
-
- clientStreamRead(http->client_stream.tail->data, http, 0,
- HTTP_REQBUF_SZ, context->reqbuf);
-
- /*
- * if we don't close() here, we still need a timeout handler!
- */
- typedef CommCbMemFunT<ConnStateData, CommTimeoutCbParams> TimeoutDialer;
- AsyncCall::Pointer timeoutCall = JobCallback(33, 5,
- TimeoutDialer, this, ConnStateData::requestTimeout);
- commSetConnTimeout(io.conn, 30, timeoutCall);
-
- /*
- * Aha, but we don't want a read handler!
- */
- Comm::SetSelect(io.fd, COMM_SELECT_READ, NULL, NULL, 0);
- }
-
-#else
/*
* Just close the connection to not confuse browsers
- * using persistent connections. Some browsers opens
- * an connection and then does not use it until much
+ * using persistent connections. Some browsers open
+ * a connection and then do not use it until much
* later (presumeably because the request triggering
* the open has already been completed on another
* connection)
*/
debugs(33, 3, "requestTimeout: FD " << io.fd << ": lifetime is expired.");
io.conn->close();
-#endif
}
static void
io.conn->close();
}
-ConnStateData *
-connStateCreate(const Comm::ConnectionPointer &client, AnyP::PortCfg *port)
+ConnStateData::ConnStateData(const MasterXaction::Pointer &xact) :
+ AsyncJob("ConnStateData"),
+#if USE_SSL
+ sslBumpMode(Ssl::bumpEnd),
+ switchedToHttps_(false),
+ sslServerBump(NULL),
+#endif
+ stoppedSending_(NULL),
+ stoppedReceiving_(NULL)
{
- ConnStateData *result = new ConnStateData;
+ pinning.host = NULL;
+ pinning.port = -1;
+ pinning.pinned = false;
+ pinning.auth = false;
+ pinning.zeroReply = false;
+ pinning.peer = NULL;
+
+ // store the details required for creating more MasterXaction objects as new requests come in
+ clientConnection = xact->tcpClient;
+ port = cbdataReference(xact->squidPort.get());
+ log_addr = xact->tcpClient->remote;
+ log_addr.applyMask(Config.Addrs.client_netmask);
- result->clientConnection = client;
- result->log_addr = client->remote;
- result->log_addr.applyMask(Config.Addrs.client_netmask);
- result->in.buf = (char *)memAllocBuf(CLIENT_REQ_BUF_SZ, &result->in.allocatedSize);
- result->port = cbdataReference(port);
+ in.buf = (char *)memAllocBuf(CLIENT_REQ_BUF_SZ, &in.allocatedSize);
if (port->disable_pmtu_discovery != DISABLE_PMTU_OFF &&
- (result->transparent() || port->disable_pmtu_discovery == DISABLE_PMTU_ALWAYS)) {
+ (transparent() || port->disable_pmtu_discovery == DISABLE_PMTU_ALWAYS)) {
#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT)
int i = IP_PMTUDISC_DONT;
- if (setsockopt(client->fd, SOL_IP, IP_MTU_DISCOVER, &i, sizeof(i)) < 0)
- debugs(33, 2, "WARNING: Path MTU discovery disabling failed on " << client << " : " << xstrerror());
+ if (setsockopt(clientConnection->fd, SOL_IP, IP_MTU_DISCOVER, &i, sizeof(i)) < 0)
+ debugs(33, 2, "WARNING: Path MTU discovery disabling failed on " << clientConnection << " : " << xstrerror());
#else
static bool reported = false;
}
typedef CommCbMemFunT<ConnStateData, CommCloseCbParams> Dialer;
- AsyncCall::Pointer call = JobCallback(33, 5, Dialer, result, ConnStateData::connStateClosed);
- comm_add_close_handler(client->fd, call);
+ AsyncCall::Pointer call = JobCallback(33, 5, Dialer, this, ConnStateData::connStateClosed);
+ comm_add_close_handler(clientConnection->fd, call);
if (Config.onoff.log_fqdn)
- fqdncache_gethostbyaddr(client->remote, FQDN_LOOKUP_IF_MISS);
+ fqdncache_gethostbyaddr(clientConnection->remote, FQDN_LOOKUP_IF_MISS);
#if USE_IDENT
if (Ident::TheConfig.identLookup) {
ACLFilledChecklist identChecklist(Ident::TheConfig.identLookup, NULL, NULL);
- identChecklist.src_addr = client->remote;
- identChecklist.my_addr = client->local;
+ identChecklist.src_addr = xact->tcpClient->remote;
+ identChecklist.my_addr = xact->tcpClient->local;
if (identChecklist.fastCheck() == ACCESS_ALLOWED)
- Ident::Start(client, clientIdentDone, result);
+ Ident::Start(xact->tcpClient, clientIdentDone, this);
}
#endif
- clientdbEstablished(client->remote, 1);
+ clientdbEstablished(clientConnection->remote, 1);
- result->flags.readMore = true;
- return result;
+ flags.readMore = true;
}
/** Handle a new connection on HTTP socket. */
void
httpAccept(const CommAcceptCbParams ¶ms)
{
- AnyP::PortCfg *s = static_cast<AnyP::PortCfg *>(params.data);
+ MasterXaction::Pointer xact = params.xaction;
+ AnyP::PortCfgPointer s = xact->squidPort;
+
+ if (!s.valid()) {
+ // it is possible the call or accept() was still queued when the port was reconfigured
+ debugs(33, 2, "HTTP accept failure: port reconfigured.");
+ return;
+ }
if (params.flag != COMM_OK) {
// Its possible the call was still queued when the client disconnected
++ incoming_sockets_accepted;
// Socket is ready, setup the connection manager to start using it
- ConnStateData *connState = connStateCreate(params.conn, s);
+ ConnStateData *connState = new ConnStateData(xact);
typedef CommCbMemFunT<ConnStateData, CommTimeoutCbParams> TimeoutDialer;
AsyncCall::Pointer timeoutCall = JobCallback(33, 5,
/**
* A callback function to use with the ACLFilledChecklist callback.
- * In the case of ACCES_ALLOWED answer initializes a bumped SSL connection,
+ * In the case of ACCESS_ALLOWED answer initializes a bumped SSL connection,
* else reverts the connection to tunnel mode.
*/
static void
static void
httpsAccept(const CommAcceptCbParams ¶ms)
{
- AnyP::PortCfg *s = static_cast<AnyP::PortCfg *>(params.data);
+ MasterXaction::Pointer xact = params.xaction;
+ const AnyP::PortCfgPointer s = xact->squidPort;
+
+ if (!s.valid()) {
+ // it is possible the call or accept() was still queued when the port was reconfigured
+ debugs(33, 2, "HTTPS accept failure: port reconfigured.");
+ return;
+ }
if (params.flag != COMM_OK) {
// Its possible the call was still queued when the client disconnected
++incoming_sockets_accepted;
// Socket is ready, setup the connection manager to start using it
- ConnStateData *connState = connStateCreate(params.conn, s);
+ ConnStateData *connState = new ConnStateData(xact);
if (s->flags.tunnelSslBumping) {
debugs(33, 5, "httpsAccept: accept transparent connection: " << params.conn);
// Try to add generated ssl context to storage.
if (port->generateHostCertificates && isNew) {
- if (signAlgorithm == Ssl::algSignTrusted)
+ if (signAlgorithm == Ssl::algSignTrusted) {
+ // Add signing certificate to the certificates chain
+ X509 *cert = port->signingCert.get();
+ if (SSL_CTX_add_extra_chain_cert(sslContext, cert)) {
+ // increase the certificate lock
+ CRYPTO_add(&(cert->references),1,CRYPTO_LOCK_X509);
+ } else {
+ const int ssl_error = ERR_get_error();
+ debugs(33, DBG_IMPORTANT, "WARNING: can not add signing certificate to SSL context chain: " << ERR_error_string(ssl_error, NULL));
+ }
Ssl::addChainToSslContext(sslContext, port->certsToChain.get());
+ }
//else it is self-signed or untrusted do not attrach any certificate
Ssl::LocalContextStorage & ssl_ctx_cache(Ssl::TheGlobalContextStorage.getLocalStorage(port->s));
#if USE_SSL
if (s->flags.tunnelSslBumping && !Config.accessList.ssl_bump) {
- debugs(33, DBG_IMPORTANT, "WARNING: No ssl_bump configured. Disabling ssl-bump on " << s->protocol << "_port " << s->s);
+ debugs(33, DBG_IMPORTANT, "WARNING: No ssl_bump configured. Disabling ssl-bump on " << URLScheme(s->transport.protocol) << "_port " << s->s);
s->flags.tunnelSslBumping = false;
}
// TODO: merge with similar code in clientHttpConnectionsOpen()
if (s->flags.tunnelSslBumping && !Config.accessList.ssl_bump) {
- debugs(33, DBG_IMPORTANT, "WARNING: No ssl_bump configured. Disabling ssl-bump on " << s->protocol << "_port " << s->s);
+ debugs(33, DBG_IMPORTANT, "WARNING: No ssl_bump configured. Disabling ssl-bump on " << URLScheme(s->transport.protocol) << "_port " << s->s);
s->flags.tunnelSslBumping = false;
}
CBDATA_CLASS_INIT(ConnStateData);
-ConnStateData::ConnStateData() :
- AsyncJob("ConnStateData"),
-#if USE_SSL
- sslBumpMode(Ssl::bumpEnd),
- switchedToHttps_(false),
- sslServerBump(NULL),
-#endif
- stoppedSending_(NULL),
- stoppedReceiving_(NULL)
-{
- pinning.host = NULL;
- pinning.port = -1;
- pinning.pinned = false;
- pinning.auth = false;
- pinning.zeroReply = false;
- pinning.peer = NULL;
-}
-
bool
ConnStateData::transparent() const
{
pinning.closeHandler = NULL; // Comm unregisters handlers before calling
const bool sawZeroReply = pinning.zeroReply; // reset when unpinning
unpinConnection();
- if (sawZeroReply) {
+ if (sawZeroReply && clientConnection != NULL) {
debugs(33, 3, "Closing client connection on pinned zero reply.");
clientConnection->close();
}
char desc[FD_DESC_SZ];
if (Comm::IsConnOpen(pinning.serverConnection)) {
- if (pinning.serverConnection->fd == pinServer->fd)
+ if (pinning.serverConnection->fd == pinServer->fd) {
+ startPinnedConnectionMonitoring();
return;
+ }
}
unpinConnection(); // closes pinned connection, if any, and resets fields
Params ¶ms = GetCommParams<Params>(pinning.closeHandler);
params.conn = pinning.serverConnection;
comm_add_close_handler(pinning.serverConnection->fd, pinning.closeHandler);
+
+ startPinnedConnectionMonitoring();
+}
+
+/// Assign a read handler to an idle pinned connection so that we can detect connection closures.
+void
+ConnStateData::startPinnedConnectionMonitoring()
+{
+ if (pinning.readHandler != NULL)
+ return; // already monitoring
+
+ typedef CommCbMemFunT<ConnStateData, CommIoCbParams> Dialer;
+ pinning.readHandler = JobCallback(33, 3,
+ Dialer, this, ConnStateData::clientPinnedConnectionRead);
+ static char unusedBuf[8];
+ comm_read(pinning.serverConnection, unusedBuf, sizeof(unusedBuf), pinning.readHandler);
+}
+
+void
+ConnStateData::stopPinnedConnectionMonitoring()
+{
+ if (pinning.readHandler != NULL) {
+ comm_read_cancel(pinning.serverConnection->fd, pinning.readHandler);
+ pinning.readHandler = NULL;
+ }
+}
+
+/// Our read handler called by Comm when the server either closes an idle pinned connection or
+/// perhaps unexpectedly sends something on that idle (from Squid p.o.v.) connection.
+void
+ConnStateData::clientPinnedConnectionRead(const CommIoCbParams &io)
+{
+ pinning.readHandler = NULL; // Comm unregisters handlers before calling
+
+ if (io.flag == COMM_ERR_CLOSING)
+ return; // close handler will clean up
+
+ // We could use getConcurrentRequestCount(), but this may be faster.
+ const bool clientIsIdle = !getCurrentContext();
+
+ debugs(33, 3, "idle pinned " << pinning.serverConnection << " read " <<
+ io.size << (clientIsIdle ? " with idle client" : ""));
+
+ assert(pinning.serverConnection == io.conn);
+ pinning.serverConnection->close();
+
+ // If we are still sending data to the client, do not close now. When we are done sending,
+ // ClientSocketContext::keepaliveNextRequest() checks pinning.serverConnection and will close.
+ // However, if we are idle, then we must close to inform the idle client and minimize races.
+ if (clientIsIdle && clientConnection != NULL)
+ clientConnection->close();
}
const Comm::ConnectionPointer
bool valid = true;
if (!Comm::IsConnOpen(pinning.serverConnection))
valid = false;
- if (pinning.auth && request && strcasecmp(pinning.host, request->GetHost()) != 0) {
+ else if (pinning.auth && pinning.host && request && strcasecmp(pinning.host, request->GetHost()) != 0)
valid = false;
- }
- if (request && pinning.port != request->port) {
+ else if (request && pinning.port != request->port)
valid = false;
- }
- if (pinning.peer && !cbdataReferenceValid(pinning.peer)) {
+ else if (pinning.peer && !cbdataReferenceValid(pinning.peer))
valid = false;
- }
- if (aPeer != pinning.peer) {
+ else if (aPeer != pinning.peer)
valid = false;
- }
if (!valid) {
/* The pinning info is not safe, remove any pinning info */