#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 "comm/Write.h"
#include "CommCalls.h"
#include "errorpage.h"
-#include "eui/Config.h"
#include "fd.h"
#include "fde.h"
-#include "forward.h"
#include "fqdncache.h"
+#include "FwdState.h"
#include "globals.h"
#include "http.h"
#include "HttpHdrContRange.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 *);
#endif
static CTCB clientLifetimeTimeout;
static ClientSocketContext *parseHttpRequestAbort(ConnStateData * conn, const char *uri);
-static ClientSocketContext *parseHttpRequest(ConnStateData *, HttpParser *, HttpRequestMethod *, HttpVersion *);
+static ClientSocketContext *parseHttpRequest(ConnStateData *, HttpParser *, HttpRequestMethod *, Http::ProtocolVersion *);
#if USE_IDENT
static IDCB clientIdentDone;
#endif
static int clientIsContentLengthValid(HttpRequest * r);
static int clientIsRequestBodyTooLargeForPolicy(int64_t bodyLength);
-static void clientUpdateStatHistCounters(log_type logType, int svc_time);
-static void clientUpdateStatCounters(log_type logType);
+static void clientUpdateStatHistCounters(LogTags logType, int svc_time);
+static void clientUpdateStatCounters(LogTags logType);
static void clientUpdateHierCounters(HierarchyLogEntry *);
static bool clientPingHasFinished(ping_data const *aPing);
void prepareLogWithRequestDetails(HttpRequest *, AccessLogEntry::Pointer &);
#endif
static int responseFinishedOrFailed(HttpReply * rep, StoreIOBuffer const &receivedData);
static void ClientSocketContextPushDeferredIfNeeded(ClientSocketContext::Pointer deferredRequest, ConnStateData * conn);
-static void clientUpdateSocketStats(log_type logType, size_t size);
+static void clientUpdateSocketStats(LogTags logType, size_t size);
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
{
void
ClientSocketContext::writeControlMsg(HttpControlMsg &msg)
{
- HttpReply *rep = msg.reply;
- Must(rep);
+ const HttpReply::Pointer rep(msg.reply);
+ Must(rep != NULL);
// apply selected clientReplyContext::buildReplyHeader() mods
// it is not clear what headers are required for control messages
#endif
void
-clientUpdateStatCounters(log_type logType)
+clientUpdateStatCounters(LogTags logType)
{
++statCounter.client_http.requests;
}
void
-clientUpdateStatHistCounters(log_type logType, int svc_time)
+clientUpdateStatHistCounters(LogTags logType, int svc_time)
{
statCounter.client_http.allSvcTime.count(svc_time);
/**
packerToMemInit(&p, &mb);
ah->lastMeta.packInto(&p);
aLogEntry->adapt.last_meta = xstrdup(mb.buf);
- aLogEntry->notes.append(&ah->metaHeaders);
}
#endif
aLogEntry->http.method = request->method;
aLogEntry->http.version = request->http_ver;
aLogEntry->hier = request->hier;
- if (request->helperNotes)
- aLogEntry->notes.append(request->helperNotes->notes);
if (request->content_length > 0) // negative when no body or unknown length
aLogEntry->cache.requestSize += request->content_length;
aLogEntry->cache.extuser = request->extacl_user.termedBuf();
-#if USE_AUTH
- if (request->auth_user_request != NULL) {
- if (request->auth_user_request->username())
- aLogEntry->cache.authuser = xstrdup(request->auth_user_request->username());
- }
-#endif
-
// Adapted request, if any, inherits and then collects all the stats, but
// the virgin request gets logged instead; copy the stats to log them.
// TODO: avoid losses by keeping these stats in a shared history object?
debugs(33, 9, "clientLogRequest: al.url='" << al->url << "'");
if (al->reply) {
- al->http.code = al->reply->sline.status;
+ al->http.code = al->reply->sline.status();
al->http.content_type = al->reply->content_type.termedBuf();
} else if (loggingEntry() && loggingEntry()->mem_obj) {
- al->http.code = loggingEntry()->mem_obj->getReply()->sline.status;
+ al->http.code = loggingEntry()->mem_obj->getReply()->sline.status();
al->http.content_type = loggingEntry()->mem_obj->getReply()->content_type.termedBuf();
}
if (loggingEntry() && loggingEntry()->mem_obj)
al->cache.objectSize = loggingEntry()->contentLen();
- al->cache.caddr.SetNoAddr();
+ al->cache.caddr.setNoAddr();
if (getConn() != NULL) {
al->cache.caddr = getConn()->log_addr;
#endif
- /*Add meta headers*/
+ /*Add notes*/
+ // The al->notes and request->notes must point to the same object.
+ // Enable the following assertion to check for possible bugs.
+ // assert(request->notes == al->notes);
typedef Notes::iterator ACAMLI;
for (ACAMLI i = Config.notes.begin(); i != Config.notes.end(); ++i) {
if (const char *value = (*i)->match(request, al->reply)) {
- al->notes.addEntry(new HttpHeaderEntry(HDR_OTHER, (*i)->key.termedBuf(), value));
+ NotePairs ¬es = SyncNotes(*al, *request);
+ notes.add((*i)->key.termedBuf(), value);
debugs(33, 3, HERE << (*i)->key.termedBuf() << " " << value);
}
}
- ACLFilledChecklist *checklist = clientAclChecklistCreate(Config.accessList.log, this);
+ ACLFilledChecklist checklist(NULL, request, NULL);
+ if (al->reply) {
+ checklist.reply = al->reply;
+ HTTPMSGLOCK(checklist.reply);
+ }
+
+ if (request) {
+ al->adapted_request = request;
+ HTTPMSGLOCK(al->adapted_request);
+ }
+ accessLogLog(al, &checklist);
- if (al->reply)
- checklist->reply = HTTPMSGLOCK(al->reply);
+ 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);
+ }
+ updatePerformanceCounters = (statsCheck.fastCheck() == ACCESS_ALLOWED);
+ }
- if (!Config.accessList.log || checklist->fastCheck() == ACCESS_ALLOWED) {
+ if (updatePerformanceCounters) {
if (request)
- al->adapted_request = HTTPMSGLOCK(request);
- accessLogLog(al, checklist);
- updateCounters();
+ updateCounters();
if (getConn() != NULL && getConn()->clientConnection != NULL)
clientdbUpdate(getConn()->clientConnection->remote, logType, AnyP::PROTO_HTTP, out.size);
}
-
- delete checklist;
}
void
deleteThis("ConnStateData::connStateClosed");
}
+#if USE_AUTH
+void
+ConnStateData::setAuth(const Auth::UserRequest::Pointer &aur, const char *by)
+{
+ if (auth_ == NULL) {
+ if (aur != NULL) {
+ debugs(33, 2, "Adding connection-auth to " << clientConnection << " from " << by);
+ auth_ = aur;
+ }
+ return;
+ }
+
+ // clobered with self-pointer
+ // NP: something nasty is going on in Squid, but harmless.
+ if (aur == auth_) {
+ debugs(33, 2, "WARNING: Ignoring duplicate connection-auth for " << clientConnection << " from " << by);
+ return;
+ }
+
+ /*
+ * Connection-auth relies on a single set of credentials being preserved
+ * for all requests on a connection once they have been setup.
+ * There are several things which need to happen to preserve security
+ * when connection-auth credentials change unexpectedly or are unset.
+ *
+ * 1) auth helper released from any active state
+ *
+ * They can only be reserved by a handshake process which this
+ * connection can now never complete.
+ * This prevents helpers hanging when their connections close.
+ *
+ * 2) pinning is expected to be removed and server conn closed
+ *
+ * The upstream link is authenticated with the same credentials.
+ * Expecting the same level of consistency we should have received.
+ * This prevents upstream being faced with multiple or missing
+ * credentials after authentication.
+ * NP: un-pin is left to the cleanup in ConnStateData::swanSong()
+ * we just trigger that cleanup here via comm_reset_close() or
+ * ConnStateData::stopReceiving()
+ *
+ * 3) the connection needs to close.
+ *
+ * This prevents attackers injecting requests into a connection,
+ * or gateways wrongly multiplexing users into a single connection.
+ *
+ * When credentials are missing closure needs to follow an auth
+ * challenge for best recovery by the client.
+ *
+ * When credentials change there is nothing we can do but abort as
+ * fast as possible. Sending TCP RST instead of an HTTP response
+ * is the best-case action.
+ */
+
+ // clobbered with nul-pointer
+ if (aur == NULL) {
+ debugs(33, 2, "WARNING: Graceful closure on " << clientConnection << " due to connection-auth erase from " << by);
+ auth_->releaseAuthServer();
+ auth_ = NULL;
+ // XXX: need to test whether the connection re-auth challenge is sent. If not, how to trigger it from here.
+ // NP: the current situation seems to fix challenge loops in Safari without visible issues in others.
+ // we stop receiving more traffic but can leave the Job running to terminate after the error or challenge is delivered.
+ stopReceiving("connection-auth removed");
+ return;
+ }
+
+ // clobbered with alternative credentials
+ if (aur != auth_) {
+ debugs(33, 2, "ERROR: Closing " << clientConnection << " due to change of connection-auth from " << by);
+ auth_->releaseAuthServer();
+ auth_ = NULL;
+ // this is a fatal type of problem.
+ // Close the connection immediately with TCP RST to abort all traffic flow
+ comm_reset_close(clientConnection);
+ return;
+ }
+
+ /* NOT REACHABLE */
+}
+#endif
+
// cleans up before destructor is called
void
ConnStateData::swanSong()
clientdbEstablished(clientConnection->remote, -1); /* decrement */
assert(areAllContextsForThisConnection());
freeAllContexts();
-#if USE_AUTH
- if (auth_user_request != NULL) {
- debugs(33, 4, "ConnStateData::swanSong: freeing auth_user_request '" << auth_user_request << "' (this is '" << this << "')");
- auth_user_request->onConnectionClose(this);
- }
-#endif
- if (Comm::IsConnOpen(pinning.serverConnection))
- pinning.serverConnection->close();
- pinning.serverConnection = NULL;
+ unpinConnection();
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
+ setAuth(NULL, "ConnStateData::SwanSong cleanup");
+#endif
BodyProducer::swanSong();
flags.swanSang = true;
RequestMethodStr(request->method));
// TODO: move to HttpRequest::hdrCacheInit, just like HttpReply.
- request->flags.proxyKeepalive = request->persistent() ? 1 : 0;
+ request->flags.proxyKeepalive = request->persistent();
}
static int
if (!rep)
range_err = "no [parse-able] reply";
- else if ((rep->sline.status != HTTP_OK) && (rep->sline.status != HTTP_PARTIAL_CONTENT))
+ else if ((rep->sline.status() != Http::scOkay) && (rep->sline.status() != Http::scPartialContent))
range_err = "wrong status code";
else if (hdr->has(HDR_CONTENT_RANGE))
range_err = "origin server does ranges";
delete http->request->range;
http->request->range = NULL;
} else {
- /* XXX: TODO: Review, this unconditional set may be wrong. - TODO: review. */
- httpStatusLineSet(&rep->sline, rep->sline.version,
- HTTP_PARTIAL_CONTENT, NULL);
+ /* XXX: TODO: Review, this unconditional set may be wrong. */
+ rep->sline.set(rep->sline.version, Http::scPartialContent);
// web server responded with a valid, but unexpected range.
// will (try-to) forward as-is.
//TODO: we should cope with multirange request/responses
context->sendBody(rep, receivedData);
else {
assert(rep);
- http->al->reply = HTTPMSGLOCK(rep);
+ http->al->reply = rep;
+ HTTPMSGLOCK(http->al->reply);
context->sendStartOfMessage(rep, receivedData);
}
}
void
-clientUpdateSocketStats(log_type logType, size_t size)
+clientUpdateSocketStats(LogTags logType, size_t size)
{
if (size == 0)
return;
char *host;
char ipbuf[MAX_IPSTRLEN];
- http->flags.accel = 1;
+ http->flags.accel = true;
/* BUG: Squid cannot deal with '*' URLs (RFC2616 5.1.2) */
#if SHOULD_REJECT_UNKNOWN_URLS
if (!url) {
- hp->request_parse_status = HTTP_BAD_REQUEST;
+ hp->request_parse_status = Http::scBadRequest;
return parseHttpRequestAbort(conn, "error:invalid-request");
}
#endif
}
if (vport < 0)
- vport = http->getConn()->clientConnection->local.GetPort();
+ vport = http->getConn()->clientConnection->local.port();
const bool switchedToHttps = conn->switchedToHttps();
const bool tryHostHeader = vhost || switchedToHttps;
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);
/* Put the local socket IP address as the hostname, with whatever vport we found */
int url_sz = strlen(url) + 32 + Config.appendDomainLen;
http->uri = (char *)xcalloc(url_sz, 1);
- http->getConn()->clientConnection->local.ToHostname(ipbuf,MAX_IPSTRLEN);
+ 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. */
int url_sz = strlen(url) + 32 + Config.appendDomainLen;
http->uri = (char *)xcalloc(url_sz, 1);
- http->getConn()->clientConnection->local.ToHostname(ipbuf,MAX_IPSTRLEN);
+ http->getConn()->clientConnection->local.toHostStr(ipbuf,MAX_IPSTRLEN);
snprintf(http->uri, url_sz, "%s://%s:%d%s",
- http->getConn()->port->protocol,
- ipbuf, http->getConn()->clientConnection->local.GetPort(), url);
+ URLScheme(http->getConn()->port->transport.protocol).const_str(),
+ ipbuf, http->getConn()->clientConnection->local.port(), url);
debugs(33, 5, "TRANSPARENT REWRITE: '" << http->uri << "'");
}
}
-/**
- * parseHttpRequest()
+/** Parse an HTTP request
*
- * Returns
- * NULL on incomplete requests
- * a ClientSocketContext structure on success or failure.
- * Sets result->flags.parsed_ok to 0 if failed to parse the request.
- * Sets result->flags.parsed_ok to 1 if we have a good request.
+ * \note Sets result->flags.parsed_ok to 0 if failed to parse the request,
+ * to 1 if the request was correctly parsed.
+ * \param[in] csd a ConnStateData. The caller must make sure it is not null
+ * \param[in] hp an HttpParser
+ * \param[out] mehtod_p will be set as a side-effect of the parsing.
+ * Pointed-to value will be set to Http::METHOD_NONE in case of
+ * parsing failure
+ * \param[out] http_ver will be set as a side-effect of the parsing
+ * \return NULL on incomplete requests,
+ * a ClientSocketContext structure on success or failure.
*/
static ClientSocketContext *
-parseHttpRequest(ConnStateData *csd, HttpParser *hp, HttpRequestMethod * method_p, HttpVersion *http_ver)
+parseHttpRequest(ConnStateData *csd, HttpParser *hp, HttpRequestMethod * method_p, Http::ProtocolVersion *http_ver)
{
char *req_hdr = NULL;
char *end;
return NULL;
} else if ( (size_t)hp->bufsiz >= Config.maxRequestHeaderSize && headersEnd(hp->buf, Config.maxRequestHeaderSize) == 0) {
debugs(33, 5, "parseHttpRequest: Too large request");
- hp->request_parse_status = HTTP_HEADER_TOO_LARGE;
+ hp->request_parse_status = Http::scHeaderTooLarge;
return parseHttpRequestAbort(csd, "error:request-too-large");
}
}
/* Request line is valid here .. */
- *http_ver = HttpVersion(hp->req.v_maj, hp->req.v_min);
+ *http_ver = Http::ProtocolVersion(hp->req.v_maj, hp->req.v_min);
/* This call scans the entire request, not just the headers */
if (hp->req.v_maj > 0) {
/* Enforce max_request_size */
if (req_sz >= Config.maxRequestHeaderSize) {
debugs(33, 5, "parseHttpRequest: Too large request");
- hp->request_parse_status = HTTP_HEADER_TOO_LARGE;
+ hp->request_parse_status = Http::scHeaderTooLarge;
return parseHttpRequestAbort(csd, "error:request-too-large");
}
*method_p = HttpRequestMethod(&hp->buf[hp->req.m_start], &hp->buf[hp->req.m_end]+1);
/* deny CONNECT via accelerated ports */
- if (*method_p == Http::METHOD_CONNECT && csd && csd->port && csd->port->accel) {
- debugs(33, DBG_IMPORTANT, "WARNING: CONNECT method received on " << csd->port->protocol << " Accelerator port " << csd->port->s.GetPort() );
+ if (*method_p == Http::METHOD_CONNECT && csd->port && csd->port->flags.accelSurrogate) {
+ 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_METHOD_NOT_ALLOWED;
+ hp->request_parse_status = Http::scMethodNotAllowed;
return parseHttpRequestAbort(csd, "error:method-not-allowed");
}
if (*method_p == Http::METHOD_NONE) {
/* XXX need a way to say "this many character length string" */
debugs(33, DBG_IMPORTANT, "clientParseRequestMethod: Unsupported method in request '" << hp->buf << "'");
- hp->request_parse_status = HTTP_METHOD_NOT_ALLOWED;
+ hp->request_parse_status = Http::scMethodNotAllowed;
return parseHttpRequestAbort(csd, "error:unsupported-request-method");
}
#endif
- debugs(33,5, HERE << "repare absolute URL from " << (csd->transparent()?"intercept":(csd->port->accel ? "accel":"")));
+ debugs(33,5, HERE << "repare absolute URL from " <<
+ (csd->transparent()?"intercept":(csd->port->flags.accelSurrogate ? "accel":"")));
/* Rewrite the URL in transparent or accelerator mode */
/* NP: there are several cases to traverse here:
* - standard mode (forward proxy)
http->uri = xstrdup(internalLocalUri(NULL, url));
// We just re-wrote the URL. Must replace the Host: header.
// But have not parsed there yet!! flag for local-only handling.
- http->flags.internal = 1;
+ http->flags.internal = true;
- } else if (csd->port->accel || csd->switchedToHttps()) {
+ } else if (csd->port->flags.accelSurrogate || csd->switchedToHttps()) {
/* accelerator mode */
prepareAcceleratedURL(csd, http, url, req_hdr);
}
clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
assert (repContext);
repContext->setReplyToError(ERR_TOO_BIG,
- HTTP_BAD_REQUEST, Http::METHOD_NONE, NULL,
+ Http::scBadRequest, Http::METHOD_NONE, NULL,
clientConnection->remote, NULL, NULL, NULL);
context->registerWithConn();
context->pullData();
// at the client-side, but many such errors do require closure and the
// client-side code is bad at handling errors so we play it safe.
if (request)
- request->flags.proxyKeepalive = 0;
+ request->flags.proxyKeepalive = false;
flags.readMore = false;
debugs(33,4, HERE << "Will close after error: " << clientConnection);
}
repContext->setReplyToStoreEntry(sslServerBump->entry);
// save the original request for logging purposes
- if (!context->http->al->request)
- context->http->al->request = HTTPMSGLOCK(http->request);
+ if (!context->http->al->request) {
+ context->http->al->request = http->request;
+ HTTPMSGLOCK(context->http->al->request);
+ }
// Get error details from the fake certificate-peeking request.
http->request->detailError(sslServerBump->request->errType, sslServerBump->request->errDetail);
// In bump-server-first mode, we have not necessarily seen the intended
// server name at certificate-peeking time. Check for domain mismatch now,
// when we can extract the intended name from the bumped HTTP request.
- if (sslServerBump->serverCert.get()) {
+ if (X509 *srvCert = sslServerBump->serverCert.get()) {
HttpRequest *request = http->request;
- if (!Ssl::checkX509ServerValidity(sslServerBump->serverCert.get(), request->GetHost())) {
+ if (!Ssl::checkX509ServerValidity(srvCert, request->GetHost())) {
debugs(33, 2, "SQUID_X509_V_ERR_DOMAIN_MISMATCH: Certificate " <<
"does not match domainname " << request->GetHost());
- ACLFilledChecklist check(Config.ssl_client.cert_error, request, dash_str);
- check.sslErrors = new Ssl::Errors(SQUID_X509_V_ERR_DOMAIN_MISMATCH);
- const bool allowDomainMismatch =
- check.fastCheck() == ACCESS_ALLOWED;
- delete check.sslErrors;
- check.sslErrors = NULL;
+ bool allowDomainMismatch = false;
+ if (Config.ssl_client.cert_error) {
+ ACLFilledChecklist check(Config.ssl_client.cert_error, request, dash_str);
+ check.sslErrors = new Ssl::CertErrors(Ssl::CertError(SQUID_X509_V_ERR_DOMAIN_MISMATCH, srvCert));
+ allowDomainMismatch = (check.fastCheck() == ACCESS_ALLOWED);
+ delete check.sslErrors;
+ check.sslErrors = NULL;
+ }
if (!allowDomainMismatch) {
quitAfterError(request);
request->hier.note(peekerRequest->hier.tcpServer, request->GetHost());
// Create an error object and fill it
- ErrorState *err = new ErrorState(ERR_SECURE_CONNECT_FAIL, HTTP_SERVICE_UNAVAILABLE, request);
+ ErrorState *err = new ErrorState(ERR_SECURE_CONNECT_FAIL, Http::scServiceUnavailable, request);
err->src_addr = clientConnection->remote;
Ssl::ErrorDetail *errDetail = new Ssl::ErrorDetail(
SQUID_X509_V_ERR_DOMAIN_MISMATCH,
- sslServerBump->serverCert.get(), NULL);
+ srvCert, NULL);
err->detail = errDetail;
// Save the original request for logging purposes.
- if (!context->http->al->request)
- context->http->al->request = HTTPMSGLOCK(request);
+ if (!context->http->al->request) {
+ context->http->al->request = request;
+ HTTPMSGLOCK(context->http->al->request);
+ }
repContext->setReplyToError(request->method, err);
assert(context->http->out.offset == 0);
context->pullData();
#endif // USE_SSL
static void
-clientProcessRequest(ConnStateData *conn, HttpParser *hp, ClientSocketContext *context, const HttpRequestMethod& method, HttpVersion http_ver)
+clientProcessRequest(ConnStateData *conn, HttpParser *hp, ClientSocketContext *context, const HttpRequestMethod& method, Http::ProtocolVersion http_ver)
{
ClientHttpRequest *http = context->http;
- HttpRequest *request = NULL;
+ HttpRequest::Pointer request;
bool notedUseOfBuffer = false;
bool chunked = false;
bool mustReplyToOptions = false;
clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
assert (repContext);
switch (hp->request_parse_status) {
- case HTTP_HEADER_TOO_LARGE:
- repContext->setReplyToError(ERR_TOO_BIG, HTTP_BAD_REQUEST, method, http->uri, conn->clientConnection->remote, NULL, conn->in.buf, NULL);
+ case Http::scHeaderTooLarge:
+ repContext->setReplyToError(ERR_TOO_BIG, Http::scBadRequest, method, http->uri, conn->clientConnection->remote, NULL, conn->in.buf, NULL);
break;
- case HTTP_METHOD_NOT_ALLOWED:
- repContext->setReplyToError(ERR_UNSUP_REQ, HTTP_METHOD_NOT_ALLOWED, method, http->uri,
+ case Http::scMethodNotAllowed:
+ repContext->setReplyToError(ERR_UNSUP_REQ, Http::scMethodNotAllowed, method, http->uri,
conn->clientConnection->remote, NULL, conn->in.buf, NULL);
break;
default:
if ((request = HttpRequest::CreateFromUrlAndMethod(http->uri, method)) == NULL) {
clientStreamNode *node = context->getClientReplyContext();
debugs(33, 5, "Invalid URL: " << http->uri);
- conn->quitAfterError(request);
+ conn->quitAfterError(request.getRaw());
// setLogUri should called before repContext->setReplyToError
setLogUri(http, http->uri, true);
clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
assert (repContext);
- repContext->setReplyToError(ERR_INVALID_URL, HTTP_BAD_REQUEST, method, http->uri, conn->clientConnection->remote, NULL, NULL, NULL);
+ repContext->setReplyToError(ERR_INVALID_URL, Http::scBadRequest, method, http->uri, conn->clientConnection->remote, NULL, NULL, NULL);
assert(context->http->out.offset == 0);
context->pullData();
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();
debugs(33, 5, "Unsupported HTTP version discovered. :\n" << HttpParserHdrBuf(hp));
- conn->quitAfterError(request);
+ conn->quitAfterError(request.getRaw());
// setLogUri should called before repContext->setReplyToError
setLogUri(http, http->uri, true);
clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
assert (repContext);
- repContext->setReplyToError(ERR_UNSUP_HTTPVERSION, HTTP_HTTP_VERSION_NOT_SUPPORTED, method, http->uri,
+ repContext->setReplyToError(ERR_UNSUP_HTTPVERSION, Http::scHttpVersionNotSupported, method, http->uri,
conn->clientConnection->remote, NULL, HttpParserHdrBuf(hp), NULL);
assert(context->http->out.offset == 0);
context->pullData();
if (http_ver.major >= 1 && !request->parseHeader(HttpParserHdrBuf(hp), HttpParserHdrSz(hp))) {
clientStreamNode *node = context->getClientReplyContext();
debugs(33, 5, "Failed to parse request headers:\n" << HttpParserHdrBuf(hp));
- conn->quitAfterError(request);
+ conn->quitAfterError(request.getRaw());
// setLogUri should called before repContext->setReplyToError
setLogUri(http, http->uri, true);
clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
assert (repContext);
- repContext->setReplyToError(ERR_INVALID_REQ, HTTP_BAD_REQUEST, method, http->uri, conn->clientConnection->remote, NULL, NULL, NULL);
+ repContext->setReplyToError(ERR_INVALID_REQ, Http::scBadRequest, method, http->uri, conn->clientConnection->remote, NULL, NULL, NULL);
assert(context->http->out.offset == 0);
context->pullData();
goto finish;
request->flags.accelerated = http->flags.accel;
request->flags.sslBumped=conn->switchedToHttps();
- request->flags.canRePin = request->flags.sslBumped && conn->pinning.pinned;
request->flags.ignoreCc = conn->port->ignore_cc;
// TODO: decouple http->flags.accel from request->flags.sslBumped
request->flags.noDirect = (request->flags.accelerated && !request->flags.sslBumped) ?
!conn->port->allow_direct : 0;
#if USE_AUTH
if (request->flags.sslBumped) {
- if (conn->auth_user_request != NULL)
- request->auth_user_request = conn->auth_user_request;
+ if (conn->getAuth() != NULL)
+ request->auth_user_request = conn->getAuth();
}
#endif
*/
if (http->clientConnection != NULL) {
request->flags.intercepted = ((http->clientConnection->flags & COMM_INTERCEPTION) != 0);
- request->flags.spoofClientIp = ((http->clientConnection->flags & COMM_TRANSPARENT) != 0 ) ;
+ request->flags.interceptTproxy = ((http->clientConnection->flags & COMM_TRANSPARENT) != 0 ) ;
+ if (request->flags.interceptTproxy) {
+ if (Config.accessList.spoof_client_ip) {
+ ACLFilledChecklist *checklist = clientAclChecklistCreate(Config.accessList.spoof_client_ip, http);
+ request->flags.spoofClientIp = (checklist->fastCheck() == ACCESS_ALLOWED);
+ delete checklist;
+ } else
+ request->flags.spoofClientIp = true;
+ } else
+ request->flags.spoofClientIp = false;
}
if (internalCheck(request->urlpath.termedBuf())) {
if (internalHostnameIs(request->GetHost()) &&
request->port == getMyPort()) {
- http->flags.internal = 1;
+ http->flags.internal = true;
} else if (Config.onoff.global_internal_static && internalStaticCheck(request->urlpath.termedBuf())) {
request->SetHost(internalHostname());
request->port = getMyPort();
- http->flags.internal = 1;
+ http->flags.internal = true;
}
}
}
request->flags.internal = http->flags.internal;
- setLogUri (http, urlCanonicalClean(request));
+ setLogUri (http, urlCanonicalClean(request.getRaw()));
request->client_addr = conn->clientConnection->remote; // XXX: remove reuest->client_addr member.
#if FOLLOW_X_FORWARDED_FOR
// indirect client gets stored here because it is an HTTP header result (from X-Forwarded-For:)
mustReplyToOptions = (method == Http::METHOD_OPTIONS) &&
(request->header.getInt64(HDR_MAX_FORWARDS) == 0);
- if (!urlCheckRequest(request) || mustReplyToOptions || unsupportedTe) {
+ if (!urlCheckRequest(request.getRaw()) || mustReplyToOptions || unsupportedTe) {
clientStreamNode *node = context->getClientReplyContext();
- conn->quitAfterError(request);
+ conn->quitAfterError(request.getRaw());
clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
assert (repContext);
- repContext->setReplyToError(ERR_UNSUP_REQ, HTTP_NOT_IMPLEMENTED, request->method, NULL,
- conn->clientConnection->remote, request, NULL, NULL);
+ repContext->setReplyToError(ERR_UNSUP_REQ, Http::scNotImplemented, request->method, NULL,
+ conn->clientConnection->remote, request.getRaw(), NULL, NULL);
assert(context->http->out.offset == 0);
context->pullData();
goto finish;
}
- if (!chunked && !clientIsContentLengthValid(request)) {
+ if (!chunked && !clientIsContentLengthValid(request.getRaw())) {
clientStreamNode *node = context->getClientReplyContext();
clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
assert (repContext);
- conn->quitAfterError(request);
+ conn->quitAfterError(request.getRaw());
repContext->setReplyToError(ERR_INVALID_REQ,
- HTTP_LENGTH_REQUIRED, request->method, NULL,
- conn->clientConnection->remote, request, NULL, NULL);
+ Http::scLengthRequired, request->method, NULL,
+ conn->clientConnection->remote, request.getRaw(), NULL, NULL);
assert(context->http->out.offset == 0);
context->pullData();
goto finish;
clientStreamNode *node = context->getClientReplyContext();
clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
assert (repContext);
- conn->quitAfterError(request);
- repContext->setReplyToError(ERR_INVALID_REQ, HTTP_EXPECTATION_FAILED, request->method, http->uri,
- conn->clientConnection->remote, request, NULL, NULL);
+ conn->quitAfterError(request.getRaw());
+ repContext->setReplyToError(ERR_INVALID_REQ, Http::scExpectationFailed, request->method, http->uri,
+ conn->clientConnection->remote, request.getRaw(), NULL, NULL);
assert(context->http->out.offset == 0);
context->pullData();
goto finish;
}
}
- http->request = HTTPMSGLOCK(request);
+ http->request = request.getRaw();
+ HTTPMSGLOCK(http->request);
clientSetKeepaliveFlag(http);
// Let tunneling code be fully responsible for CONNECT requests
clientStreamNode *node = context->getClientReplyContext();
clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
assert (repContext);
- conn->quitAfterError(request);
+ conn->quitAfterError(request.getRaw());
repContext->setReplyToError(ERR_TOO_BIG,
- HTTP_REQUEST_ENTITY_TOO_LARGE, Http::METHOD_NONE, NULL,
+ Http::scRequestEntityTooLarge, Http::METHOD_NONE, NULL,
conn->clientConnection->remote, http->request, NULL, NULL);
assert(context->http->out.offset == 0);
context->pullData();
* be freed and the above connNoteUseOfBuffer() would hit an
* assertion, not to mention that we were accessing freed memory.
*/
- if (request && request->flags.resetTcp && Comm::IsConnOpen(conn->clientConnection)) {
+ if (request != NULL && request->flags.resetTcp && Comm::IsConnOpen(conn->clientConnection)) {
debugs(33, 3, HERE << "Sending TCP RST on " << conn->clientConnection);
conn->flags.readMore = false;
comm_reset_close(conn->clientConnection);
}
}
-static int
-connOkToAddRequest(ConnStateData * conn)
+/**
+ * Limit the number of concurrent requests.
+ * \return true when there are available position(s) in the pipeline queue for another request.
+ * \return false when the pipeline queue is full or disabled.
+ */
+bool
+ConnStateData::concurrentRequestQueueFilled() const
{
- int result = conn->getConcurrentRequestCount() < (Config.onoff.pipeline_prefetch ? 2 : 1);
+ const int existingRequestCount = getConcurrentRequestCount();
+
+ // default to the configured pipeline size.
+ // add 1 because the head of pipeline is counted in concurrent requests and not prefetch queue
+ const int concurrentRequestLimit = Config.pipeline_max_prefetch + 1;
- if (!result) {
- debugs(33, 3, HERE << conn->clientConnection << " max concurrent requests reached");
- debugs(33, 5, HERE << conn->clientConnection << " defering new request until one is done");
+ // when queue filled already we cant add more.
+ if (existingRequestCount >= concurrentRequestLimit) {
+ debugs(33, 3, clientConnection << " max concurrent requests reached (" << concurrentRequestLimit << ")");
+ debugs(33, 5, clientConnection << " deferring new request until one is done");
+ return true;
}
- return result;
+ return false;
}
/**
{
HttpRequestMethod method;
bool parsed_req = false;
- HttpVersion http_ver;
debugs(33, 5, HERE << clientConnection << ": attempting to parse");
if (in.notYetUsed == 0)
break;
- /* Limit the number of concurrent requests to 2 */
- if (!connOkToAddRequest(this)) {
+ /* Limit the number of concurrent requests */
+ if (concurrentRequestQueueFilled())
break;
- }
/* Should not be needed anymore */
/* Terminate the string */
HttpParserInit(&parser_, in.buf, in.notYetUsed);
/* Process request */
+ Http::ProtocolVersion http_ver;
ClientSocketContext *context = parseHttpRequest(this, &parser_, &method, &http_ver);
PROF_stop(parseHttpRequest);
}
/* It might be half-closed, we can't tell */
- fd_table[io.conn->fd].flags.socket_eof = 1;
+ fd_table[io.conn->fd].flags.socket_eof = true;
commMarkHalfClosed(io.conn->fd);
clientStreamNode *node = context->getClientReplyContext();
clientReplyContext *repContext = dynamic_cast<clientReplyContext*>(node->data.getRaw());
assert(repContext);
- const http_status scode = (error == ERR_TOO_BIG) ?
- HTTP_REQUEST_ENTITY_TOO_LARGE : HTTP_BAD_REQUEST;
+ const Http::StatusCode scode = (error == ERR_TOO_BIG) ?
+ Http::scRequestEntityTooLarge : HTTP_BAD_REQUEST;
repContext->setReplyToError(error, scode,
repContext->http->request->method,
repContext->http->uri,
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_REQUEST_TIMEOUT, 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;
- setsockopt(client->fd, SOL_IP, IP_MTU_DISCOVER, &i, sizeof i);
+ 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 int reported = 0;
+ static bool reported = false;
if (!reported) {
- debugs(33, DBG_IMPORTANT, "Notice: httpd_accel_no_pmtu_disc not supported on your platform");
- reported = 1;
+ debugs(33, DBG_IMPORTANT, "NOTICE: Path MTU discovery disabling is not supported on your platform.");
+ reported = true;
}
#endif
}
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
-#if USE_SQUID_EUI
- if (Eui::TheConfig.euiLookup) {
- if (client->remote.IsIPv4()) {
- result->clientConnection->remoteEui48.lookup(client->remote);
- } else if (client->remote.IsIPv6()) {
- result->clientConnection->remoteEui64.lookup(client->remote);
- }
- }
-#endif
+ clientdbEstablished(clientConnection->remote, 1);
- clientdbEstablished(client->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,
else {
char buf[MAX_IPSTRLEN];
assert(bumpMode != Ssl::bumpNone && bumpMode != Ssl::bumpEnd);
- HttpRequest *fakeRequest = new HttpRequest;
- fakeRequest->SetHost(details->local.NtoA(buf, sizeof(buf)));
- fakeRequest->port = details->local.GetPort();
+ HttpRequest::Pointer fakeRequest(new HttpRequest);
+ fakeRequest->SetHost(details->local.toStr(buf, sizeof(buf)));
+ fakeRequest->port = details->local.port();
fakeRequest->clientConnectionManager = connState;
fakeRequest->client_addr = connState->clientConnection->remote;
#if FOLLOW_X_FORWARDED_FOR
fakeRequest->indirect_client_addr = connState->clientConnection->remote;
#endif
fakeRequest->my_addr = connState->clientConnection->local;
- fakeRequest->flags.spoofClientIp = ((connState->clientConnection->flags & COMM_TRANSPARENT) != 0 ) ;
+ fakeRequest->flags.interceptTproxy = ((connState->clientConnection->flags & COMM_TRANSPARENT) != 0 ) ;
fakeRequest->flags.intercepted = ((connState->clientConnection->flags & COMM_INTERCEPTION) != 0);
+ fakeRequest->myportname = connState->port->name;
+ if (fakeRequest->flags.interceptTproxy) {
+ if (Config.accessList.spoof_client_ip) {
+ ACLFilledChecklist checklist(Config.accessList.spoof_client_ip, fakeRequest.getRaw(), NULL);
+ fakeRequest->flags.spoofClientIp = (checklist.fastCheck() == ACCESS_ALLOWED);
+ } else
+ fakeRequest->flags.spoofClientIp = true;
+ } else
+ fakeRequest->flags.spoofClientIp = false;
debugs(33, 4, HERE << details << " try to generate a Dynamic SSL CTX");
- connState->switchToHttps(fakeRequest, bumpMode);
+ connState->switchToHttps(fakeRequest.getRaw(), bumpMode);
}
}
/**
* 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
// fake a CONNECT request to force connState to tunnel
static char ip[MAX_IPSTRLEN];
static char reqStr[MAX_IPSTRLEN + 80];
- connState->clientConnection->local.NtoA(ip, sizeof(ip));
- snprintf(reqStr, sizeof(reqStr), "CONNECT %s:%d HTTP/1.1\r\nHost: %s\r\n\r\n", ip, connState->clientConnection->local.GetPort(), ip);
+ connState->clientConnection->local.toUrl(ip, sizeof(ip));
+ snprintf(reqStr, sizeof(reqStr), "CONNECT %s HTTP/1.1\r\nHost: %s\r\n\r\n", ip, ip);
bool ret = connState->handleReadData(reqStr, strlen(reqStr));
if (ret)
ret = connState->clientParseRequests();
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->sslBump) {
+ if (s->flags.tunnelSslBumping) {
debugs(33, 5, "httpsAccept: accept transparent connection: " << params.conn);
if (!Config.accessList.ssl_bump) {
HttpRequest *request = new HttpRequest();
static char ip[MAX_IPSTRLEN];
assert(params.conn->flags & (COMM_TRANSPARENT | COMM_INTERCEPTION));
- request->SetHost(params.conn->local.NtoA(ip, sizeof(ip)));
- request->port = params.conn->local.GetPort();
+ request->SetHost(params.conn->local.toStr(ip, sizeof(ip)));
+ request->port = params.conn->local.port();
request->myportname = s->name;
ACLFilledChecklist *acl_checklist = new ACLFilledChecklist(Config.accessList.ssl_bump, request, NULL);
if (X509 *mimicCert = sslServerBump->serverCert.get())
certProperties.mimicCert.resetAndLock(mimicCert);
- ACLFilledChecklist checklist(NULL, sslServerBump->request,
+ ACLFilledChecklist checklist(NULL, sslServerBump->request.getRaw(),
clientConnection != NULL ? clientConnection->rfc931 : dash_str);
checklist.sslErrors = cbdataReference(sslServerBump->sslErrors);
debugs(33, 5, HERE << "Finding SSL certificate for " << sslBumpCertKey << " in cache");
Ssl::LocalContextStorage & ssl_ctx_cache(Ssl::TheGlobalContextStorage.getLocalStorage(port->s));
- SSL_CTX * dynCtx = ssl_ctx_cache.find(sslBumpCertKey.termedBuf());
- if (dynCtx) {
+ SSL_CTX * dynCtx = NULL;
+ Ssl::SSL_CTX_Pointer *cachedCtx = ssl_ctx_cache.get(sslBumpCertKey.termedBuf());
+ if (cachedCtx && (dynCtx = cachedCtx->get())) {
debugs(33, 5, HERE << "SSL certificate for " << sslBumpCertKey << " have found in cache");
if (Ssl::verifySslCertificate(dynCtx, certProperties)) {
debugs(33, 5, HERE << "Cached SSL certificate for " << sslBumpCertKey << " is valid");
return;
} else {
debugs(33, 5, HERE << "Cached SSL certificate for " << sslBumpCertKey << " is out of date. Delete this certificate from cache");
- ssl_ctx_cache.remove(sslBumpCertKey.termedBuf());
+ ssl_ctx_cache.del(sslBumpCertKey.termedBuf());
}
} else {
debugs(33, 5, HERE << "SSL certificate for " << sslBumpCertKey << " haven't found in cache");
// 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));
assert(sslBumpCertKey.defined() && sslBumpCertKey[0] != '\0');
if (sslContext) {
- if (!ssl_ctx_cache.add(sslBumpCertKey.termedBuf(), sslContext)) {
+ if (!ssl_ctx_cache.add(sslBumpCertKey.termedBuf(), new Ssl::SSL_CTX_Pointer(sslContext))) {
// If it is not in storage delete after using. Else storage deleted it.
fd_table[clientConnection->fd].dynamicSslContext = sslContext;
}
}
}
- SSL *ssl = NULL;
- if (!(ssl = httpsCreate(clientConnection, sslContext)))
+ if (!httpsCreate(clientConnection, sslContext))
return;
// commSetConnTimeout() was called for this request before we switched.
// and now want to switch to SSL to send the error to the client
// without even peeking at the origin server certificate.
if (bumpServerMode == Ssl::bumpServerFirst && !sslServerBump) {
- request->flags.sslPeek = 1;
+ request->flags.sslPeek = true;
sslServerBump = new Ssl::ServerBump(request);
// will call httpsPeeked() with certificate and connection, eventually
- FwdState::fwdStart(clientConnection, sslServerBump->entry, sslServerBump->request);
+ FwdState::fwdStart(clientConnection, sslServerBump->entry, sslServerBump->request.getRaw());
return;
}
debugs(33, 5, HERE << "Error while bumping: " << sslConnectHostOrIp);
Ip::Address intendedDest;
intendedDest = sslConnectHostOrIp.termedBuf();
- const bool isConnectRequest = !port->spoof_client_ip && !port->intercepted;
+ const bool isConnectRequest = !port->flags.isIntercepted();
// Squid serves its own error page and closes, so we want
// a CN that causes no additional browser errors. Possible
// only when bumping CONNECT with a user-typed address.
- if (intendedDest.IsAnyAddr() || isConnectRequest)
+ if (intendedDest.isAnyAddr() || isConnectRequest)
sslCommonName = sslConnectHostOrIp;
else if (sslServerBump->serverCert.get())
sslCommonName = Ssl::CommonHostName(sslServerBump->serverCert.get());
}
#if USE_SSL
- if (s->sslBump && !Config.accessList.ssl_bump) {
- debugs(33, DBG_IMPORTANT, "WARNING: No ssl_bump configured. Disabling ssl-bump on " << s->protocol << "_port " << s->s);
- s->sslBump = 0;
+ if (s->flags.tunnelSslBumping && !Config.accessList.ssl_bump) {
+ debugs(33, DBG_IMPORTANT, "WARNING: No ssl_bump configured. Disabling ssl-bump on " << URLScheme(s->transport.protocol) << "_port " << s->s);
+ s->flags.tunnelSslBumping = false;
}
- if (s->sslBump && !s->staticSslContext && !s->generateHostCertificates) {
+ if (s->flags.tunnelSslBumping &&
+ !s->staticSslContext &&
+ !s->generateHostCertificates) {
debugs(1, DBG_IMPORTANT, "Will not bump SSL at http_port " << s->s << " due to SSL initialization failure.");
- s->sslBump = 0;
+ s->flags.tunnelSslBumping = false;
}
- if (s->sslBump) {
+ if (s->flags.tunnelSslBumping) {
// Create ssl_ctx cache for this port.
Ssl::TheGlobalContextStorage.addLocalStorage(s->s, s->dynamicCertMemCacheSize == std::numeric_limits<size_t>::max() ? 4194304 : s->dynamicCertMemCacheSize);
}
// then pass back when active so we can start a TcpAcceptor subscription.
s->listenConn = new Comm::Connection;
s->listenConn->local = s->s;
- s->listenConn->flags = COMM_NONBLOCKING | (s->spoof_client_ip ? COMM_TRANSPARENT : 0) | (s->intercepted ? COMM_INTERCEPTION : 0);
+ s->listenConn->flags = COMM_NONBLOCKING | (s->flags.tproxyIntercept ? COMM_TRANSPARENT : 0) | (s->flags.natIntercept ? COMM_INTERCEPTION : 0);
// setup the subscriptions such that new connections accepted by listenConn are handled by HTTP
typedef CommCbFunPtrCallT<CommAcceptCbPtrFun> AcceptCall;
}
// 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->s);
- s->sslBump = 0;
+ if (s->flags.tunnelSslBumping && !Config.accessList.ssl_bump) {
+ debugs(33, DBG_IMPORTANT, "WARNING: No ssl_bump configured. Disabling ssl-bump on " << URLScheme(s->transport.protocol) << "_port " << s->s);
+ s->flags.tunnelSslBumping = false;
}
- if (s->sslBump && !s->staticSslContext && !s->generateHostCertificates) {
+ if (s->flags.tunnelSslBumping && !s->staticSslContext && !s->generateHostCertificates) {
debugs(1, DBG_IMPORTANT, "Will not bump SSL at http_port " << s->s << " due to SSL initialization failure.");
- s->sslBump = 0;
+ s->flags.tunnelSslBumping = false;
}
- if (s->sslBump) {
+ if (s->flags.tunnelSslBumping) {
// 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->listenConn = new Comm::Connection;
s->listenConn->local = s->s;
- s->listenConn->flags = COMM_NONBLOCKING | (s->spoof_client_ip ? COMM_TRANSPARENT : 0) |
- (s->intercepted ? COMM_INTERCEPTION : 0);
+ s->listenConn->flags = COMM_NONBLOCKING | (s->flags.tproxyIntercept ? COMM_TRANSPARENT : 0) |
+ (s->flags.natIntercept ? COMM_INTERCEPTION : 0);
// setup the subscriptions such that new connections accepted by listenConn are handled by HTTPS
typedef CommCbFunPtrCallT<CommAcceptCbPtrFun> AcceptCall;
AsyncJob::Start(new Comm::TcpAcceptor(s->listenConn, FdNote(portTypeNote), sub));
debugs(1, DBG_IMPORTANT, "Accepting " <<
- (s->intercepted ? "NAT intercepted " : "") <<
- (s->spoof_client_ip ? "TPROXY spoofing " : "") <<
- (s->sslBump ? "SSL bumped " : "") <<
- (s->accel ? "reverse-proxy " : "")
+ (s->flags.natIntercept ? "NAT intercepted " : "") <<
+ (s->flags.tproxyIntercept ? "TPROXY intercepted " : "") <<
+ (s->flags.tunnelSslBumping ? "SSL bumped " : "") <<
+ (s->flags.accelSurrogate ? "reverse-proxy " : "")
<< FdNote(portTypeNote) << " connections at "
<< s->listenConn);
CBDATA_CLASS_INIT(ConnStateData);
-ConnStateData::ConnStateData() :
- AsyncJob("ConnStateData"),
-#if USE_SSL
- sslBumpMode(Ssl::bumpEnd),
- switchedToHttps_(false),
- sslServerBump(NULL),
-#endif
- stoppedSending_(NULL),
- stoppedReceiving_(NULL)
-{
- pinning.pinned = false;
- pinning.auth = false;
-}
-
bool
ConnStateData::transparent() const
{
{
// FwdState might repin a failed connection sooner than this close
// callback is called for the failed connection.
- if (pinning.serverConnection == io.conn) {
- pinning.closeHandler = NULL; // Comm unregisters handlers before calling
- unpinConnection();
+ assert(pinning.serverConnection == io.conn);
+ pinning.closeHandler = NULL; // Comm unregisters handlers before calling
+ const bool sawZeroReply = pinning.zeroReply; // reset when unpinning
+ unpinConnection();
+ 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
pinning.port = request->port;
pinnedHost = pinning.host;
} else {
- pinning.port = pinServer->remote.GetPort();
+ pinning.port = pinServer->remote.port();
}
pinning.pinned = true;
if (aPeer)
char stmp[MAX_IPSTRLEN];
snprintf(desc, FD_DESC_SZ, "%s pinned connection for %s (%d)",
(auth || !aPeer) ? pinnedHost : aPeer->name,
- clientConnection->remote.ToURL(stmp,MAX_IPSTRLEN),
+ clientConnection->remote.toUrl(stmp,MAX_IPSTRLEN),
clientConnection->fd);
fd_note(pinning.serverConnection->fd, desc);
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 */
safe_free(pinning.host);
+ pinning.zeroReply = false;
+
/* NOTE: pinning.pinned should be kept. This combined with fd == -1 at the end of a request indicates that the host
* connection has gone away */
}