/*
- * Copyright (C) 1996-2015 The Squid Software Foundation and contributors
+ * Copyright (C) 1996-2016 The Squid Software Foundation and contributors
*
* Squid software is distributed under GPLv2+ license and includes
* contributions from numerous individuals and organizations.
* data, or sending it.
*
\par
- * ClientKeepAliveNextRequest will then detect the presence of data in
+ * ConnStateData::kick() will then detect the presence of data in
* the next ClientHttpRequest, and will send it, restablishing the
* data flow.
*/
#include "http.h"
#include "http/one/RequestParser.h"
#include "http/one/TeChunkedParser.h"
+#include "http/Stream.h"
#include "HttpHdrContRange.h"
#include "HttpHeaderTools.h"
#include "HttpReply.h"
#include "parser/Tokenizer.h"
#include "profiler/Profiler.h"
#include "rfc1738.h"
+#include "security/NegotiationHistory.h"
#include "servers/forward.h"
#include "SquidConfig.h"
#include "SquidTime.h"
#include "ssl/ServerBump.h"
#include "ssl/support.h"
#endif
-#if USE_SSL_CRTD
-#include "ssl/certificate_db.h"
-#include "ssl/crtd_message.h"
-#endif
// for tvSubUsec() which should be in SquidTime.h
#include "util.h"
static void clientListenerConnectionOpened(AnyP::PortCfgPointer &s, const Ipc::FdNoteId portTypeNote, const Subscription::Pointer &sub);
-/* our socket-related context */
-
-CBDATA_CLASS_INIT(ClientSocketContext);
-
-/* Local functions */
-static IOCB clientWriteComplete;
-static IOCB clientWriteBodyComplete;
static IOACB httpAccept;
#if USE_OPENSSL
static IOACB httpsAccept;
static void clientUpdateHierCounters(HierarchyLogEntry *);
static bool clientPingHasFinished(ping_data const *aPing);
void prepareLogWithRequestDetails(HttpRequest *, AccessLogEntry::Pointer &);
-static void ClientSocketContextPushDeferredIfNeeded(ClientSocketContext::Pointer deferredRequest, ConnStateData * conn);
-static void clientUpdateSocketStats(const LogTags &logType, size_t size);
+static void ClientSocketContextPushDeferredIfNeeded(Http::StreamPointer deferredRequest, ConnStateData * conn);
char *skipLeadingSpace(char *aString);
-clientStreamNode *
-ClientSocketContext::getTail() const
-{
- if (http->client_stream.tail)
- return (clientStreamNode *)http->client_stream.tail->data;
-
- return NULL;
-}
-
-clientStreamNode *
-ClientSocketContext::getClientReplyContext() const
-{
- return (clientStreamNode *)http->client_stream.tail->prev->data;
-}
-
-ConnStateData *
-ClientSocketContext::getConn() const
-{
- return http->getConn();
-}
-
-void
-ClientSocketContext::removeFromConnectionList(ConnStateData * conn)
-{
- ClientSocketContext::Pointer *tempContextPointer;
- assert(conn != NULL && cbdataReferenceValid(conn));
- assert(conn->getCurrentContext() != NULL);
- /* Unlink us from the connection request list */
- tempContextPointer = & conn->currentobject;
-
- while (tempContextPointer->getRaw()) {
- if (*tempContextPointer == this)
- break;
-
- tempContextPointer = &(*tempContextPointer)->next;
- }
-
- assert(tempContextPointer->getRaw() != NULL);
- *tempContextPointer = next;
- next = NULL;
-}
-
-ClientSocketContext::~ClientSocketContext()
-{
- clientStreamNode *node = getTail();
-
- if (node) {
- ClientSocketContext *streamContext = dynamic_cast<ClientSocketContext *> (node->data.getRaw());
-
- if (streamContext) {
- /* We are *always* the tail - prevent recursive free */
- assert(this == streamContext);
- node->data = NULL;
- }
- }
-
- if (connRegistered_)
- deRegisterWithConn();
-
- httpRequestFree(http);
-
- /* clean up connection links to us */
- assert(this != next.getRaw());
-}
-
-void
-ClientSocketContext::registerWithConn()
-{
- assert (!connRegistered_);
- assert (http);
- assert (http->getConn() != NULL);
- connRegistered_ = true;
- http->getConn()->pipeline.add(ClientSocketContext::Pointer(this));
-}
-
-void
-ClientSocketContext::deRegisterWithConn()
-{
- assert (connRegistered_);
- removeFromConnectionList(http->getConn());
- connRegistered_ = false;
-}
-
-void
-ClientSocketContext::connIsFinished()
-{
- assert (http);
- assert (http->getConn() != NULL);
- ConnStateData *conn = http->getConn();
- deRegisterWithConn();
- /* we can't handle any more stream data - detach */
- clientStreamDetach(getTail(), http);
-
- conn->kick(); // kick anything which was waiting for us to finish
-}
-
-ClientSocketContext::ClientSocketContext(const Comm::ConnectionPointer &aConn, ClientHttpRequest *aReq) :
- clientConnection(aConn),
- http(aReq),
- reply(NULL),
- next(NULL),
- writtenToSocket(0),
- mayUseConnection_ (false),
- connRegistered_ (false)
-{
- assert(http != NULL);
- memset (reqbuf, '\0', sizeof (reqbuf));
- flags.deferred = 0;
- flags.parsed_ok = 0;
- deferredparams.node = NULL;
- deferredparams.rep = NULL;
-}
-
-void
-ClientSocketContext::writeControlMsg(HttpControlMsg &msg)
-{
- HttpReply::Pointer rep(msg.reply);
- Must(rep != NULL);
-
- // remember the callback
- cbControlMsgSent = msg.cbSuccess;
-
- AsyncCall::Pointer call = commCbCall(33, 5, "ClientSocketContext::wroteControlMsg",
- CommIoCbPtrFun(&WroteControlMsg, this));
-
- getConn()->writeControlMsgAndCall(this, rep.getRaw(), call);
-}
-
-/// called when we wrote the 1xx response
-void
-ClientSocketContext::wroteControlMsg(const Comm::ConnectionPointer &conn, char *, size_t, Comm::Flag errflag, int xerrno)
-{
- if (errflag == Comm::ERR_CLOSING)
- return;
-
- if (errflag == Comm::OK) {
- ScheduleCallHere(cbControlMsgSent);
- return;
- }
-
- debugs(33, 3, HERE << "1xx writing failed: " << xstrerr(xerrno));
- // no error notification: see HttpControlMsg.h for rationale and
- // note that some errors are detected elsewhere (e.g., close handler)
-
- // close on 1xx errors to be conservative and to simplify the code
- // (if we do not close, we must notify the source of a failure!)
- conn->close();
-
- // XXX: writeControlMsgAndCall() should handle writer-specific writing
- // results, including errors and then call us with success/failure outcome.
-}
-
-/// wroteControlMsg() wrapper: ClientSocketContext is not an AsyncJob
-void
-ClientSocketContext::WroteControlMsg(const Comm::ConnectionPointer &conn, char *bufnotused, size_t size, Comm::Flag errflag, int xerrno, void *data)
-{
- ClientSocketContext *context = static_cast<ClientSocketContext*>(data);
- context->wroteControlMsg(conn, bufnotused, size, errflag, xerrno);
-}
-
#if USE_IDENT
static void
clientIdentDone(const char *ident, void *data)
aLogEntry->http.method = request->method;
aLogEntry->http.version = request->http_ver;
aLogEntry->hier = request->hier;
- if (request->content_length > 0) // negative when no body or unknown length
- aLogEntry->http.clientRequestSz.payloadData += request->content_length; // XXX: actually adaptedRequest payload size ??
aLogEntry->cache.extuser = request->extacl_user.termedBuf();
// Adapted request, if any, inherits and then collects all the stats, but
al->cache.objectSize = loggingEntry()->contentLen(); // payload duplicate ?? with or without TE ?
al->http.clientRequestSz.header = req_sz;
+ // the virgin request is saved to al->request
+ if (al->request && al->request->body_pipe)
+ al->http.clientRequestSz.payloadData = al->request->body_pipe->producedSize();
al->http.clientReplySz.header = out.headers_sz;
// XXX: calculate without payload encoding or headers !!
al->http.clientReplySz.payloadData = out.size - out.headers_sz; // pretend its all un-encoded data for now.
}
if (request) {
+ HTTPMSGUNLOCK(al->adapted_request);
al->adapted_request = request;
HTTPMSGLOCK(al->adapted_request);
}
ConnStateData::swanSong()
{
debugs(33, 2, HERE << clientConnection);
+ checkLogging();
+
flags.readMore = false;
- DeregisterRunner(this);
clientdbEstablished(clientConnection->remote, -1); /* decrement */
pipeline.terminateAll(0);
return 0;
}
-// careful: the "current" context may be gone if we wrote an early response
-ClientSocketContext::Pointer
-ConnStateData::getCurrentContext() const
-{
- return currentobject;
-}
-
-void
-ClientSocketContext::deferRecipientForLater(clientStreamNode * node, HttpReply * rep, StoreIOBuffer receivedData)
-{
- debugs(33, 2, "clientSocketRecipient: Deferring request " << http->uri);
- assert(flags.deferred == 0);
- flags.deferred = 1;
- deferredparams.node = node;
- deferredparams.rep = rep;
- deferredparams.queuedBuffer = receivedData;
- return;
-}
-
-bool
-ClientSocketContext::startOfOutput() const
-{
- return http->out.size == 0;
-}
-
-size_t
-ClientSocketContext::lengthToSend(Range<int64_t> const &available)
-{
- /*the size of available range can always fit in a size_t type*/
- size_t maximum = (size_t)available.size();
-
- if (!http->request->range)
- return maximum;
-
- assert (canPackMoreRanges());
-
- if (http->range_iter.debt() == -1)
- return maximum;
-
- assert (http->range_iter.debt() > 0);
-
- /* TODO this + the last line could be a range intersection calculation */
- if (available.start < http->range_iter.currentSpec()->offset)
- return 0;
-
- return min(http->range_iter.debt(), (int64_t)maximum);
-}
-
-void
-ClientSocketContext::noteSentBodyBytes(size_t bytes)
-{
- debugs(33, 7, bytes << " body bytes");
-
- http->out.offset += bytes;
-
- if (!http->request->range)
- return;
-
- if (http->range_iter.debt() != -1) {
- http->range_iter.debt(http->range_iter.debt() - bytes);
- assert (http->range_iter.debt() >= 0);
- }
-
- /* debt() always stops at -1, below that is a bug */
- assert (http->range_iter.debt() >= -1);
-}
-
bool
ClientHttpRequest::multipartRangeRequest() const
{
return request->multipartRangeRequest();
}
-bool
-ClientSocketContext::multipartRangeRequest() const
-{
- return http->multipartRangeRequest();
-}
-
-void
-ClientSocketContext::sendBody(HttpReply * rep, StoreIOBuffer bodyData)
-{
- assert(rep == NULL);
-
- if (!multipartRangeRequest() && !http->request->flags.chunkedReply) {
- size_t length = lengthToSend(bodyData.range());
- noteSentBodyBytes (length);
- AsyncCall::Pointer call = commCbCall(33, 5, "clientWriteBodyComplete",
- CommIoCbPtrFun(clientWriteBodyComplete, this));
- Comm::Write(clientConnection, bodyData.data, length, call, NULL);
- return;
- }
-
- MemBuf mb;
- mb.init();
- if (multipartRangeRequest())
- packRange(bodyData, &mb);
- else
- packChunk(bodyData, mb);
-
- if (mb.contentSize()) {
- /* write */
- AsyncCall::Pointer call = commCbCall(33, 5, "clientWriteComplete",
- CommIoCbPtrFun(clientWriteComplete, this));
- Comm::Write(clientConnection, &mb, call);
- } else
- writeComplete(clientConnection, NULL, 0, Comm::OK);
-}
-
-/**
- * Packs bodyData into mb using chunked encoding. Packs the last-chunk
- * if bodyData is empty.
- */
void
-ClientSocketContext::packChunk(const StoreIOBuffer &bodyData, MemBuf &mb)
-{
- const uint64_t length =
- static_cast<uint64_t>(lengthToSend(bodyData.range()));
- noteSentBodyBytes(length);
-
- mb.appendf("%" PRIX64 "\r\n", length);
- mb.append(bodyData.data, length);
- mb.append("\r\n", 2);
-}
-
-/** put terminating boundary for multiparts */
-static void
-clientPackTermBound(String boundary, MemBuf * mb)
+clientPackTermBound(String boundary, MemBuf *mb)
{
mb->appendf("\r\n--" SQUIDSTRINGPH "--\r\n", SQUIDSTRINGPRINT(boundary));
- debugs(33, 6, "clientPackTermBound: buf offset: " << mb->size);
+ debugs(33, 6, "buf offset: " << mb->size);
}
-/** appends a "part" HTTP header (as in a multi-part/range reply) to the buffer */
-static void
+void
clientPackRangeHdr(const HttpReply * rep, const HttpHdrRangeSpec * spec, String boundary, MemBuf * mb)
{
HttpHeader hdr(hoReply);
assert(spec);
/* put boundary */
- debugs(33, 5, "clientPackRangeHdr: appending boundary: " << boundary);
+ debugs(33, 5, "appending boundary: " << boundary);
/* rfc2046 requires to _prepend_ boundary with <crlf>! */
mb->appendf("\r\n--" SQUIDSTRINGPH "\r\n", SQUIDSTRINGPRINT(boundary));
mb->append("\r\n", 2);
}
-/**
- * extracts a "range" from *buf and appends them to mb, updating
- * all offsets and such.
- */
-void
-ClientSocketContext::packRange(StoreIOBuffer const &source, MemBuf * mb)
-{
- HttpHdrRangeIter * i = &http->range_iter;
- Range<int64_t> available (source.range());
- char const *buf = source.data;
-
- while (i->currentSpec() && available.size()) {
- const size_t copy_sz = lengthToSend(available);
-
- if (copy_sz) {
- /*
- * intersection of "have" and "need" ranges must not be empty
- */
- assert(http->out.offset < i->currentSpec()->offset + i->currentSpec()->length);
- assert(http->out.offset + (int64_t)available.size() > i->currentSpec()->offset);
-
- /*
- * put boundary and headers at the beginning of a range in a
- * multi-range
- */
-
- if (http->multipartRangeRequest() && i->debt() == i->currentSpec()->length) {
- assert(http->memObject());
- clientPackRangeHdr(
- http->memObject()->getReply(), /* original reply */
- i->currentSpec(), /* current range */
- i->boundary, /* boundary, the same for all */
- mb);
- }
-
- /*
- * append content
- */
- debugs(33, 3, "clientPackRange: appending " << copy_sz << " bytes");
-
- noteSentBodyBytes (copy_sz);
-
- mb->append(buf, copy_sz);
-
- /*
- * update offsets
- */
- available.start += copy_sz;
-
- buf += copy_sz;
-
- }
-
- if (!canPackMoreRanges()) {
- debugs(33, 3, "clientPackRange: Returning because !canPackMoreRanges.");
-
- if (i->debt() == 0)
- /* put terminating boundary for multiparts */
- clientPackTermBound(i->boundary, mb);
-
- return;
- }
-
- int64_t nextOffset = getNextRangeOffset();
-
- assert (nextOffset >= http->out.offset);
-
- int64_t skip = nextOffset - http->out.offset;
-
- /* adjust for not to be transmitted bytes */
- http->out.offset = nextOffset;
-
- if (available.size() <= (uint64_t)skip)
- return;
-
- available.start += skip;
-
- buf += skip;
-
- if (copy_sz == 0)
- return;
- }
-}
-
/** returns expected content length for multi-range replies
* note: assumes that httpHdrRangeCanonize has already been called
* warning: assumes that HTTP headers for individual ranges at the
return clen;
}
-/**
- * returns true if If-Range specs match reply, false otherwise
- */
-static int
-clientIfRangeMatch(ClientHttpRequest * http, HttpReply * rep)
-{
- const TimeOrTag spec = http->request->header.getTimeOrTag(Http::HdrType::IF_RANGE);
- /* check for parsing falure */
-
- if (!spec.valid)
- return 0;
-
- /* got an ETag? */
- if (spec.tag.str) {
- ETag rep_tag = rep->header.getETag(Http::HdrType::ETAG);
- debugs(33, 3, "clientIfRangeMatch: ETags: " << spec.tag.str << " and " <<
- (rep_tag.str ? rep_tag.str : "<none>"));
-
- if (!rep_tag.str)
- return 0; /* entity has no etag to compare with! */
-
- if (spec.tag.weak || rep_tag.weak) {
- debugs(33, DBG_IMPORTANT, "clientIfRangeMatch: Weak ETags are not allowed in If-Range: " << spec.tag.str << " ? " << rep_tag.str);
- return 0; /* must use strong validator for sub-range requests */
- }
-
- return etagIsStrongEqual(rep_tag, spec.tag);
- }
-
- /* got modification time? */
- if (spec.time >= 0) {
- return http->storeEntry()->lastmod <= spec.time;
- }
-
- assert(0); /* should not happen */
- return 0;
-}
-
/**
* generates a "unique" boundary string for multipart responses
* the caller is responsible for cleaning the string */
return b;
}
-/** adds appropriate Range headers if needed */
-void
-ClientSocketContext::buildRangeHeader(HttpReply * rep)
-{
- HttpHeader *hdr = rep ? &rep->header : 0;
- const char *range_err = NULL;
- HttpRequest *request = http->request;
- assert(request->range);
- /* check if we still want to do ranges */
-
- int64_t roffLimit = request->getRangeOffsetLimit();
-
- if (!rep)
- range_err = "no [parse-able] reply";
- else if ((rep->sline.status() != Http::scOkay) && (rep->sline.status() != Http::scPartialContent))
- range_err = "wrong status code";
- else if (hdr->has(Http::HdrType::CONTENT_RANGE))
- range_err = "origin server does ranges";
- else if (rep->content_length < 0)
- range_err = "unknown length";
- else if (rep->content_length != http->memObject()->getReply()->content_length)
- range_err = "INCONSISTENT length"; /* a bug? */
-
- /* hits only - upstream CachePeer determines correct behaviour on misses, and client_side_reply determines
- * hits candidates
- */
- else if (http->logType.isTcpHit() && http->request->header.has(Http::HdrType::IF_RANGE) && !clientIfRangeMatch(http, rep))
- range_err = "If-Range match failed";
- else if (!http->request->range->canonize(rep))
- range_err = "canonization failed";
- else if (http->request->range->isComplex())
- range_err = "too complex range header";
- else if (!http->logType.isTcpHit() && http->request->range->offsetLimitExceeded(roffLimit))
- range_err = "range outside range_offset_limit";
-
- /* get rid of our range specs on error */
- if (range_err) {
- /* XXX We do this here because we need canonisation etc. However, this current
- * code will lead to incorrect store offset requests - the store will have the
- * offset data, but we won't be requesting it.
- * So, we can either re-request, or generate an error
- */
- http->request->ignoreRange(range_err);
- } else {
- /* 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
- bool replyMatchRequest = rep->content_range != NULL ?
- request->range->contains(rep->content_range->spec) :
- true;
- const int spec_count = http->request->range->specs.size();
- int64_t actual_clen = -1;
-
- debugs(33, 3, "clientBuildRangeHeader: range spec count: " <<
- spec_count << " virgin clen: " << rep->content_length);
- assert(spec_count > 0);
- /* append appropriate header(s) */
-
- if (spec_count == 1) {
- if (!replyMatchRequest) {
- hdr->delById(Http::HdrType::CONTENT_RANGE);
- hdr->putContRange(rep->content_range);
- actual_clen = rep->content_length;
- //http->range_iter.pos = rep->content_range->spec.begin();
- (*http->range_iter.pos)->offset = rep->content_range->spec.offset;
- (*http->range_iter.pos)->length = rep->content_range->spec.length;
-
- } else {
- HttpHdrRange::iterator pos = http->request->range->begin();
- assert(*pos);
- /* append Content-Range */
-
- if (!hdr->has(Http::HdrType::CONTENT_RANGE)) {
- /* No content range, so this was a full object we are
- * sending parts of.
- */
- httpHeaderAddContRange(hdr, **pos, rep->content_length);
- }
-
- /* set new Content-Length to the actual number of bytes
- * transmitted in the message-body */
- actual_clen = (*pos)->length;
- }
- } else {
- /* multipart! */
- /* generate boundary string */
- http->range_iter.boundary = http->rangeBoundaryStr();
- /* delete old Content-Type, add ours */
- hdr->delById(Http::HdrType::CONTENT_TYPE);
- httpHeaderPutStrf(hdr, Http::HdrType::CONTENT_TYPE,
- "multipart/byteranges; boundary=\"" SQUIDSTRINGPH "\"",
- SQUIDSTRINGPRINT(http->range_iter.boundary));
- /* Content-Length is not required in multipart responses
- * but it is always nice to have one */
- actual_clen = http->mRangeCLen();
- /* http->out needs to start where we want data at */
- http->out.offset = http->range_iter.currentSpec()->offset;
- }
-
- /* replace Content-Length header */
- assert(actual_clen >= 0);
-
- hdr->delById(Http::HdrType::CONTENT_LENGTH);
-
- hdr->putInt64(Http::HdrType::CONTENT_LENGTH, actual_clen);
-
- debugs(33, 3, "clientBuildRangeHeader: actual content length: " << actual_clen);
-
- /* And start the range iter off */
- http->range_iter.updateSpec();
- }
-}
-
-void
-ClientSocketContext::prepareReply(HttpReply * rep)
-{
- reply = rep;
-
- if (http->request->range)
- buildRangeHeader(rep);
-}
-
-void
-ClientSocketContext::sendStartOfMessage(HttpReply * rep, StoreIOBuffer bodyData)
-{
- prepareReply(rep);
- assert (rep);
- MemBuf *mb = rep->pack();
-
- // dump now, so we dont output any body.
- debugs(11, 2, "HTTP Client " << clientConnection);
- debugs(11, 2, "HTTP Client REPLY:\n---------\n" << mb->buf << "\n----------");
-
- /* Save length of headers for persistent conn checks */
- http->out.headers_sz = mb->contentSize();
-#if HEADERS_LOG
-
- headersLog(0, 0, http->request->method, rep);
-#endif
-
- if (bodyData.data && bodyData.length) {
- if (multipartRangeRequest())
- packRange(bodyData, mb);
- else if (http->request->flags.chunkedReply) {
- packChunk(bodyData, *mb);
- } else {
- size_t length = lengthToSend(bodyData.range());
- noteSentBodyBytes (length);
-
- mb->append(bodyData.data, length);
- }
- }
-
- /* write */
- debugs(33,7, HERE << "sendStartOfMessage schedules clientWriteComplete");
- AsyncCall::Pointer call = commCbCall(33, 5, "clientWriteComplete",
- CommIoCbPtrFun(clientWriteComplete, this));
- Comm::Write(clientConnection, mb, call);
- delete mb;
-}
-
/**
* Write a chunk of data to a client socket. If the reply is present,
* send the reply headers down the wire too, and clean them up when
*/
assert(cbdataReferenceValid(node));
assert(node->node.next == NULL);
- ClientSocketContext::Pointer context = dynamic_cast<ClientSocketContext *>(node->data.getRaw());
+ Http::StreamPointer context = dynamic_cast<Http::Stream *>(node->data.getRaw());
assert(context != NULL);
/* TODO: check offset is what we asked for */
- if (context != http->getConn()->getCurrentContext())
+ // TODO: enforces HTTP/1 MUST on pipeline order, but is irrelevant to HTTP/2
+ if (context != http->getConn()->pipeline.front())
context->deferRecipientForLater(node, rep, receivedData);
else
http->getConn()->handleReply(rep, receivedData);
/* Set null by ContextFree */
assert(node->node.next == NULL);
/* this is the assert discussed above */
- assert(NULL == dynamic_cast<ClientSocketContext *>(node->data.getRaw()));
+ assert(NULL == dynamic_cast<Http::Stream *>(node->data.getRaw()));
/* We are only called when the client socket shutsdown.
* Tell the prev pipeline member we're finished
*/
clientStreamDetach(node, http);
}
-static void
-clientWriteBodyComplete(const Comm::ConnectionPointer &conn, char *, size_t size, Comm::Flag errflag, int xerrno, void *data)
-{
- debugs(33,7, "schedule clientWriteComplete");
- clientWriteComplete(conn, NULL, size, errflag, xerrno, data);
-}
-
void
ConnStateData::readNextRequest()
{
}
static void
-ClientSocketContextPushDeferredIfNeeded(ClientSocketContext::Pointer deferredRequest, ConnStateData * conn)
+ClientSocketContextPushDeferredIfNeeded(Http::StreamPointer deferredRequest, ConnStateData * conn)
{
debugs(33, 2, HERE << conn->clientConnection << " Sending next");
*/
}
-/// called when we have successfully finished writing the response
-void
-ClientSocketContext::keepaliveNextRequest()
-{
- debugs(33, 3, "ConnnStateData(" << http->getConn()->clientConnection << "), Context(" << clientConnection << ")");
-
- // mark ourselves as completed
- connIsFinished();
-}
-
void
ConnStateData::kick()
{
+ if (!Comm::IsConnOpen(clientConnection)) {
+ debugs(33, 2, clientConnection << " Connection was closed");
+ return;
+ }
+
if (pinning.pinned && !Comm::IsConnOpen(pinning.serverConnection)) {
debugs(33, 2, clientConnection << " Connection was pinned but server side gone. Terminating client connection");
clientConnection->close();
/** \par
* Either we need to kick-start another read or, if we have
- * a half-closed connection, kill it after the last request.
- * This saves waiting for half-closed connections to finished being
- * half-closed _AND_ then, sometimes, spending "Timeout" time in
- * the keepalive "Waiting for next request" state.
- */
- if (commIsHalfClosed(clientConnection->fd) && pipeline.empty()) {
- debugs(33, 3, "half-closed client with no pending requests, closing");
- clientConnection->close();
- return;
- }
-
- ClientSocketContext::Pointer deferredRequest;
-
- /** \par
- * At this point we either have a parsed request (which we've
- * kicked off the processing for) or not. If we have a deferred
- * request (parsed but deferred for pipeling processing reasons)
- * then look at processing it. If not, simply kickstart
- * another read.
- */
-
- if ((deferredRequest = getCurrentContext()).getRaw()) {
- debugs(33, 3, clientConnection << ": calling PushDeferredIfNeeded");
- ClientSocketContextPushDeferredIfNeeded(deferredRequest, this);
- } else if (flags.readMore) {
- debugs(33, 3, clientConnection << ": calling readNextRequest()");
- readNextRequest();
- } else {
- // XXX: Can this happen? CONNECT tunnels have deferredRequest set.
- debugs(33, DBG_IMPORTANT, MYNAME << "abandoning " << clientConnection);
- }
-}
-
-void
-clientUpdateSocketStats(const LogTags &logType, size_t size)
-{
- if (size == 0)
- return;
-
- statCounter.client_http.kbytes_out += size;
-
- if (logType.isTcpHit())
- statCounter.client_http.hit_kbytes_out += size;
-}
-
-/**
- * increments iterator "i"
- * used by clientPackMoreRanges
- *
- \retval true there is still data available to pack more ranges
- \retval false
- */
-bool
-ClientSocketContext::canPackMoreRanges() const
-{
- /** first update iterator "i" if needed */
-
- if (!http->range_iter.debt()) {
- debugs(33, 5, HERE << "At end of current range spec for " << clientConnection);
-
- if (http->range_iter.pos != http->range_iter.end)
- ++http->range_iter.pos;
-
- http->range_iter.updateSpec();
- }
-
- assert(!http->range_iter.debt() == !http->range_iter.currentSpec());
-
- /* paranoid sync condition */
- /* continue condition: need_more_data */
- debugs(33, 5, "ClientSocketContext::canPackMoreRanges: returning " << (http->range_iter.currentSpec() ? true : false));
- return http->range_iter.currentSpec() ? true : false;
-}
-
-int64_t
-ClientSocketContext::getNextRangeOffset() const
-{
- debugs (33, 5, "range: " << http->request->range <<
- "; http offset " << http->out.offset <<
- "; reply " << reply);
-
- // XXX: This method is called from many places, including pullData() which
- // may be called before prepareReply() [on some Squid-generated errors].
- // Hence, we may not even know yet whether we should honor/do ranges.
-
- if (http->request->range) {
- /* offset in range specs does not count the prefix of an http msg */
- /* check: reply was parsed and range iterator was initialized */
- assert(http->range_iter.valid);
- /* filter out data according to range specs */
- assert (canPackMoreRanges());
- {
- int64_t start; /* offset of still missing data */
- assert(http->range_iter.currentSpec());
- start = http->range_iter.currentSpec()->offset + http->range_iter.currentSpec()->length - http->range_iter.debt();
- debugs(33, 3, "clientPackMoreRanges: in: offset: " << http->out.offset);
- debugs(33, 3, "clientPackMoreRanges: out:"
- " start: " << start <<
- " spec[" << http->range_iter.pos - http->request->range->begin() << "]:" <<
- " [" << http->range_iter.currentSpec()->offset <<
- ", " << http->range_iter.currentSpec()->offset + http->range_iter.currentSpec()->length << "),"
- " len: " << http->range_iter.currentSpec()->length <<
- " debt: " << http->range_iter.debt());
- if (http->range_iter.currentSpec()->length != -1)
- assert(http->out.offset <= start); /* we did not miss it */
-
- return start;
- }
-
- } else if (reply && reply->content_range) {
- /* request does not have ranges, but reply does */
- /** \todo FIXME: should use range_iter_pos on reply, as soon as reply->content_range
- * becomes HttpHdrRange rather than HttpHdrRangeSpec.
- */
- return http->out.offset + reply->content_range->spec.offset;
- }
-
- return http->out.offset;
-}
-
-void
-ClientSocketContext::pullData()
-{
- debugs(33, 5, reply << " written " << http->out.size << " into " << clientConnection);
-
- /* More data will be coming from the stream. */
- StoreIOBuffer readBuffer;
- /* XXX: Next requested byte in the range sequence */
- /* XXX: length = getmaximumrangelenfgth */
- readBuffer.offset = getNextRangeOffset();
- readBuffer.length = HTTP_REQBUF_SZ;
- readBuffer.data = reqbuf;
- /* we may note we have reached the end of the wanted ranges */
- clientStreamRead(getTail(), http, readBuffer);
-}
-
-/** Adapt stream status to account for Range cases
- *
- */
-clientStream_status_t
-ClientSocketContext::socketState()
-{
- switch (clientStreamStatus(getTail(), http)) {
-
- case STREAM_NONE:
- /* check for range support ending */
-
- if (http->request->range) {
- /* check: reply was parsed and range iterator was initialized */
- assert(http->range_iter.valid);
- /* filter out data according to range specs */
-
- if (!canPackMoreRanges()) {
- debugs(33, 5, HERE << "Range request at end of returnable " <<
- "range sequence on " << clientConnection);
- // we got everything we wanted from the store
- return STREAM_COMPLETE;
- }
- } else if (reply && reply->content_range) {
- /* reply has content-range, but Squid is not managing ranges */
- const int64_t &bytesSent = http->out.offset;
- const int64_t &bytesExpected = reply->content_range->spec.length;
-
- debugs(33, 7, HERE << "body bytes sent vs. expected: " <<
- bytesSent << " ? " << bytesExpected << " (+" <<
- reply->content_range->spec.offset << ")");
-
- // did we get at least what we expected, based on range specs?
-
- if (bytesSent == bytesExpected) // got everything
- return STREAM_COMPLETE;
-
- if (bytesSent > bytesExpected) // Error: Sent more than expected
- return STREAM_UNPLANNED_COMPLETE;
- }
-
- return STREAM_NONE;
-
- case STREAM_COMPLETE:
- return STREAM_COMPLETE;
-
- case STREAM_UNPLANNED_COMPLETE:
- return STREAM_UNPLANNED_COMPLETE;
-
- case STREAM_FAILED:
- return STREAM_FAILED;
- }
-
- fatal ("unreachable code\n");
- return STREAM_NONE;
-}
-
-/**
- * A write has just completed to the client, or we have just realised there is
- * no more data to send.
- */
-void
-clientWriteComplete(const Comm::ConnectionPointer &conn, char *bufnotused, size_t size, Comm::Flag errflag, int, void *data)
-{
- ClientSocketContext *context = (ClientSocketContext *)data;
- context->writeComplete(conn, bufnotused, size, errflag);
-}
-
-/// remembers the abnormal connection termination for logging purposes
-void
-ClientSocketContext::noteIoError(const int xerrno)
-{
- if (http) {
- http->logType.err.timedout = (xerrno == ETIMEDOUT);
- // aborted even if xerrno is zero (which means read abort/eof)
- http->logType.err.aborted = (xerrno != ETIMEDOUT);
+ * a half-closed connection, kill it after the last request.
+ * This saves waiting for half-closed connections to finished being
+ * half-closed _AND_ then, sometimes, spending "Timeout" time in
+ * the keepalive "Waiting for next request" state.
+ */
+ if (commIsHalfClosed(clientConnection->fd) && pipeline.empty()) {
+ debugs(33, 3, "half-closed client with no pending requests, closing");
+ clientConnection->close();
+ return;
}
-}
-
-void
-ClientSocketContext::doClose()
-{
- clientConnection->close();
-}
-/// called when we encounter a response-related error
-void
-ClientSocketContext::initiateClose(const char *reason)
-{
- http->getConn()->stopSending(reason); // closes ASAP
+ /** \par
+ * At this point we either have a parsed request (which we've
+ * kicked off the processing for) or not. If we have a deferred
+ * request (parsed but deferred for pipeling processing reasons)
+ * then look at processing it. If not, simply kickstart
+ * another read.
+ */
+ Http::StreamPointer deferredRequest = pipeline.front();
+ if (deferredRequest != nullptr) {
+ debugs(33, 3, clientConnection << ": calling PushDeferredIfNeeded");
+ ClientSocketContextPushDeferredIfNeeded(deferredRequest, this);
+ } else if (flags.readMore) {
+ debugs(33, 3, clientConnection << ": calling readNextRequest()");
+ readNextRequest();
+ } else {
+ // XXX: Can this happen? CONNECT tunnels have deferredRequest set.
+ debugs(33, DBG_IMPORTANT, MYNAME << "abandoning " << clientConnection);
+ }
}
void
}
void
-ClientSocketContext::writeComplete(const Comm::ConnectionPointer &conn, char *, size_t size, Comm::Flag errflag)
+ConnStateData::afterClientWrite(size_t size)
{
- const StoreEntry *entry = http->storeEntry();
- http->out.size += size;
- debugs(33, 5, HERE << conn << ", sz " << size <<
- ", err " << errflag << ", off " << http->out.size << ", len " <<
- (entry ? entry->objectLen() : 0));
- clientUpdateSocketStats(http->logType, size);
-
- /* Bail out quickly on Comm::ERR_CLOSING - close handlers will tidy up */
-
- if (errflag == Comm::ERR_CLOSING || !Comm::IsConnOpen(conn))
- return;
-
- if (errflag || clientHttpRequestStatus(conn->fd, http)) {
- initiateClose("failure or true request status");
- /* Do we leak here ? */
- return;
- }
-
- switch (socketState()) {
-
- case STREAM_NONE:
- pullData();
- break;
-
- case STREAM_COMPLETE:
- debugs(33, 5, conn << " Stream complete, keepalive is " << http->request->flags.proxyKeepalive);
- if (http->request->flags.proxyKeepalive)
- keepaliveNextRequest();
- else
- initiateClose("STREAM_COMPLETE NOKEEPALIVE");
- return;
-
- case STREAM_UNPLANNED_COMPLETE:
- initiateClose("STREAM_UNPLANNED_COMPLETE");
- return;
-
- case STREAM_FAILED:
- initiateClose("STREAM_FAILED");
+ if (pipeline.empty())
return;
- default:
- fatal("Hit unreachable code in clientWriteComplete\n");
+ auto ctx = pipeline.front();
+ if (size) {
+ statCounter.client_http.kbytes_out += size;
+ if (ctx->http->logType.isTcpHit())
+ statCounter.client_http.hit_kbytes_out += size;
}
+ ctx->writeComplete(size);
}
-ClientSocketContext *
+Http::Stream *
ConnStateData::abortRequestParsing(const char *const uri)
{
ClientHttpRequest *http = new ClientHttpRequest(this);
http->req_sz = inBuf.length();
http->uri = xstrdup(uri);
setLogUri (http, uri);
- ClientSocketContext *context = new ClientSocketContext(clientConnection, http);
+ auto *context = new Http::Stream(clientConnection, http);
StoreIOBuffer tempBuffer;
tempBuffer.data = context->reqbuf;
tempBuffer.length = HTTP_REQBUF_SZ;
// swanSong() in the close handler will cleanup.
if (Comm::IsConnOpen(clientConnection))
clientConnection->close();
-
- // deregister now to ensure finalShutdown() does not kill us prematurely.
- // fd_table purge will cleanup if close handler was not fast enough.
- DeregisterRunner(this);
}
char *
} // else nothing to alter port-wise.
const int url_sz = hp->requestUri().length() + 32 + Config.appendDomainLen + strlen(host);
http->uri = (char *)xcalloc(url_sz, 1);
- snprintf(http->uri, url_sz, "%s://%s" SQUIDSBUFPH, AnyP::UriScheme(conn->transferProtocol.protocol).c_str(), host, SQUIDSBUFPRINT(url));
+ const SBuf &scheme = AnyP::UriScheme(conn->transferProtocol.protocol).image();
+ snprintf(http->uri, url_sz, SQUIDSBUFPH "://%s" SQUIDSBUFPH, SQUIDSBUFPRINT(scheme), host, SQUIDSBUFPRINT(url));
debugs(33, 5, "ACCEL VHOST REWRITE: " << http->uri);
} else if (conn->port->defaultsite /* && !vhost */) {
debugs(33, 5, "ACCEL DEFAULTSITE REWRITE: defaultsite=" << conn->port->defaultsite << " + vport=" << vport);
if (vport > 0) {
snprintf(vportStr, sizeof(vportStr),":%d",vport);
}
- snprintf(http->uri, url_sz, "%s://%s%s" SQUIDSBUFPH,
- AnyP::UriScheme(conn->transferProtocol.protocol).c_str(), conn->port->defaultsite, vportStr, SQUIDSBUFPRINT(url));
+ const SBuf &scheme = AnyP::UriScheme(conn->transferProtocol.protocol).image();
+ snprintf(http->uri, url_sz, SQUIDSBUFPH "://%s%s" SQUIDSBUFPH,
+ SQUIDSBUFPRINT(scheme), conn->port->defaultsite, vportStr, SQUIDSBUFPRINT(url));
debugs(33, 5, "ACCEL DEFAULTSITE REWRITE: " << http->uri);
} else if (vport > 0 /* && (!vhost || no Host:) */) {
debugs(33, 5, "ACCEL VPORT REWRITE: *_port IP + vport=" << vport);
const int url_sz = hp->requestUri().length() + 32 + Config.appendDomainLen;
http->uri = (char *)xcalloc(url_sz, 1);
http->getConn()->clientConnection->local.toHostStr(ipbuf,MAX_IPSTRLEN);
- snprintf(http->uri, url_sz, "%s://%s:%d" SQUIDSBUFPH,
- AnyP::UriScheme(conn->transferProtocol.protocol).c_str(),
- ipbuf, vport, SQUIDSBUFPRINT(url));
+ const SBuf &scheme = AnyP::UriScheme(conn->transferProtocol.protocol).image();
+ snprintf(http->uri, url_sz, SQUIDSBUFPH "://%s:%d" SQUIDSBUFPH,
+ SQUIDSBUFPRINT(scheme), ipbuf, vport, SQUIDSBUFPRINT(url));
debugs(33, 5, "ACCEL VPORT REWRITE: " << http->uri);
}
}
const int url_sz = hp->requestUri().length() + 32 + Config.appendDomainLen +
strlen(host);
http->uri = (char *)xcalloc(url_sz, 1);
- snprintf(http->uri, url_sz, "%s://%s" SQUIDSBUFPH,
- AnyP::UriScheme(conn->transferProtocol.protocol).c_str(), host, SQUIDSBUFPRINT(hp->requestUri()));
+ const SBuf &scheme = AnyP::UriScheme(conn->transferProtocol.protocol).image();
+ snprintf(http->uri, url_sz, SQUIDSBUFPH "://%s" SQUIDSBUFPH,
+ SQUIDSBUFPRINT(scheme), host, SQUIDSBUFPRINT(hp->requestUri()));
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);
static char ipbuf[MAX_IPSTRLEN];
http->getConn()->clientConnection->local.toHostStr(ipbuf,MAX_IPSTRLEN);
- snprintf(http->uri, url_sz, "%s://%s:%d" SQUIDSBUFPH,
- AnyP::UriScheme(http->getConn()->transferProtocol.protocol).c_str(),
+ const SBuf &scheme = AnyP::UriScheme(http->getConn()->transferProtocol.protocol).image();
+ snprintf(http->uri, url_sz, SQUIDSBUFPH "://%s:%d" SQUIDSBUFPH,
+ SQUIDSBUFPRINT(scheme),
ipbuf, http->getConn()->clientConnection->local.port(), SQUIDSBUFPRINT(hp->requestUri()));
debugs(33, 5, "TRANSPARENT REWRITE: " << http->uri);
}
* 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.
+ * a Http::Stream on success or failure.
*/
-ClientSocketContext *
+Http::Stream *
parseHttpRequest(ConnStateData *csd, const Http1::RequestParserPointer &hp)
{
/* Attempt to parse the first line; this will define where the method, url, version and header begin */
}
if (!parsedOk) {
- if (hp->parseStatusCode == Http::scRequestHeaderFieldsTooLarge || hp->parseStatusCode == Http::scUriTooLong)
- return csd->abortRequestParsing("error:request-too-large");
-
- return csd->abortRequestParsing("error:invalid-request");
+ const bool tooBig =
+ hp->parseStatusCode == Http::scRequestHeaderFieldsTooLarge ||
+ hp->parseStatusCode == Http::scUriTooLong;
+ auto result = csd->abortRequestParsing(
+ tooBig ? "error:request-too-large" : "error:invalid-request");
+ // assume that remaining leftovers belong to this bad request
+ csd->consumeInput(csd->inBuf.length());
+ return result;
}
}
ClientHttpRequest *http = new ClientHttpRequest(csd);
http->req_sz = hp->messageHeaderSize();
- ClientSocketContext *result = new ClientSocketContext(csd->clientConnection, http);
+ Http::Stream *result = new Http::Stream(csd->clientConnection, http);
StoreIOBuffer tempBuffer;
tempBuffer.data = result->reqbuf;
}
#if USE_OPENSSL
-bool ConnStateData::serveDelayedError(ClientSocketContext *context)
+bool ConnStateData::serveDelayedError(Http::Stream *context)
{
ClientHttpRequest *http = context->http;
// 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 (X509 *srvCert = sslServerBump->serverCert.get()) {
+ if (const Security::CertPointer &srvCert = sslServerBump->serverCert) {
HttpRequest *request = http->request;
- if (!Ssl::checkX509ServerValidity(srvCert, request->url.host())) {
+ if (!Ssl::checkX509ServerValidity(srvCert.get(), request->url.host())) {
debugs(33, 2, "SQUID_X509_V_ERR_DOMAIN_MISMATCH: Certificate " <<
"does not match domainname " << request->url.host());
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));
+ check.sslErrors = new Security::CertErrors(Security::CertError(SQUID_X509_V_ERR_DOMAIN_MISMATCH, srvCert));
allowDomainMismatch = (check.fastCheck() == ACCESS_ALLOWED);
delete check.sslErrors;
check.sslErrors = NULL;
err->src_addr = clientConnection->remote;
Ssl::ErrorDetail *errDetail = new Ssl::ErrorDetail(
SQUID_X509_V_ERR_DOMAIN_MISMATCH,
- srvCert, NULL);
+ srvCert.get(), nullptr);
err->detail = errDetail;
// Save the original request for logging purposes.
if (!context->http->al->request) {
* or false otherwise
*/
bool
-clientTunnelOnError(ConnStateData *conn, ClientSocketContext *context, HttpRequest *request, const HttpRequestMethod& method, err_type requestError, Http::StatusCode errStatusCode, const char *requestErrorBytes)
+clientTunnelOnError(ConnStateData *conn, Http::Stream *context, HttpRequest *request, const HttpRequestMethod& method, err_type requestError, Http::StatusCode errStatusCode, const char *requestErrorBytes)
{
if (conn->port->flags.isIntercepted() &&
Config.accessList.on_unsupported_protocol && conn->pipeline.nrequests <= 1) {
allow_t answer = checklist.fastCheck();
if (answer == ACCESS_ALLOWED && answer.kind == 1) {
debugs(33, 3, "Request will be tunneled to server");
- if (context)
- context->removeFromConnectionList(conn);
+ if (context) {
+ // XXX: Either the context is finished() or it should stay queued.
+ // The below may leak client streams BodyPipe objects. BUT, we need
+ // to check if client-streams detatch is safe to do here (finished() will detatch).
+ assert(conn->pipeline.front() == context); // XXX: still assumes HTTP/1 semantics
+ conn->pipeline.popMe(Http::StreamPointer(context));
+ }
Comm::SetSelect(conn->clientConnection->fd, COMM_SELECT_READ, NULL, NULL, 0);
- conn->fakeAConnectRequest("unknown-protocol", conn->preservedClientData);
- return true;
+ return conn->fakeAConnectRequest("unknown-protocol", conn->preservedClientData);
} else {
debugs(33, 3, "Continue with returning the error: " << requestError);
}
}
void
-clientProcessRequest(ConnStateData *conn, const Http1::RequestParserPointer &hp, ClientSocketContext *context)
+clientProcessRequest(ConnStateData *conn, const Http1::RequestParserPointer &hp, Http::Stream *context)
{
ClientHttpRequest *http = context->http;
bool chunked = false;
// TODO: decouple http->flags.accel from request->flags.sslBumped
request->flags.noDirect = (request->flags.accelerated && !request->flags.sslBumped) ?
!conn->port->allow_direct : 0;
+ request->sources |= isFtp ? HttpMsg::srcFtp :
+ ((request->flags.sslBumped || conn->port->transport.protocol == AnyP::PROTO_HTTPS) ? HttpMsg::srcHttps : HttpMsg::srcHttp);
#if USE_AUTH
if (request->flags.sslBumped) {
if (conn->getAuth() != NULL)
http->flags.internal = true;
} else if (Config.onoff.global_internal_static && internalStaticCheck(request->url.path())) {
debugs(33, 2, "internal URL found: " << request->url.getScheme() << "://" << request->url.authority(true) << " (global_internal_static on)");
- request->url.setScheme(AnyP::PROTO_HTTP);
+ request->url.setScheme(AnyP::PROTO_HTTP, "http");
request->url.host(internalHostname());
request->url.port(getMyPort());
http->flags.internal = true;
// On errors, bodyPipe may become nil, but readMore will be cleared
while (!inBuf.isEmpty() && !bodyPipe && flags.readMore) {
- /* Don't try to parse if the buffer is empty */
- if (inBuf.isEmpty())
- break;
-
/* Limit the number of concurrent requests */
if (concurrentRequestQueueFilled())
break;
if (needProxyProtocolHeader_ && !parseProxyProtocolHeader())
break;
- if (ClientSocketContext *context = parseOneRequest()) {
+ if (Http::Stream *context = parseOneRequest()) {
debugs(33, 5, clientConnection << ": done parsing a request");
AsyncCall::Pointer timeoutCall = commCbCall(5, 4, "clientLifetimeTimeout",
void
ConnStateData::afterClientRead()
{
+#if USE_OPENSSL
+ if (parsingTlsHandshake) {
+ parseTlsHandshake();
+ return;
+ }
+#endif
+
/* Process next request */
if (pipeline.empty())
fd_note(clientConnection->fd, "Reading next request");
// but if we fail when the server connection is used already, the server may send
// us its response too, causing various assertions. How to prevent that?
#if WE_KNOW_HOW_TO_SEND_ERRORS
- ClientSocketContext::Pointer context = getCurrentContext();
+ Http::StreamPointer context = pipeline.front();
if (context != NULL && !context->http->out.offset) { // output nothing yet
clientStreamNode *node = context->getClientReplyContext();
clientReplyContext *repContext = dynamic_cast<clientReplyContext*>(node->data.getRaw());
receivedFirstByte();
return;
}
- } else if (fd_table[io.conn->fd].ssl == NULL)
+ } else if (!fd_table[io.conn->fd].ssl)
#endif
{
const HttpRequestMethod method;
needProxyProtocolHeader_(false),
#if USE_OPENSSL
switchedToHttps_(false),
+ parsingTlsHandshake(false),
sslServerBump(NULL),
signAlgorithm(Ssl::algSignTrusted),
#endif
// register to receive notice of Squid signal events
// which may affect long persisting client connections
- RegisterRunner(this);
+ registerRunner();
}
void
(transparent() || port->disable_pmtu_discovery == DISABLE_PMTU_ALWAYS)) {
#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT)
int i = IP_PMTUDISC_DONT;
- 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());
+ if (setsockopt(clientConnection->fd, SOL_IP, IP_MTU_DISCOVER, &i, sizeof(i)) < 0) {
+ int xerrno = errno;
+ debugs(33, 2, "WARNING: Path MTU discovery disabling failed on " << clientConnection << " : " << xstrerr(xerrno));
+ }
#else
static bool reported = false;
++incoming_sockets_accepted;
// Socket is ready, setup the connection manager to start using it
- ConnStateData *connState = Http::NewServer(xact);
- AsyncJob::Start(connState); // usually async-calls readSomeData()
+ auto *srv = Http::NewServer(xact);
+ AsyncJob::Start(srv); // usually async-calls readSomeData()
}
#if USE_OPENSSL
/** Create SSL connection structure and update fd_table */
-static Security::SessionPointer
-httpsCreate(const Comm::ConnectionPointer &conn, Security::ContextPointer sslContext)
+static bool
+httpsCreate(const Comm::ConnectionPointer &conn, Security::ContextPtr sslContext)
{
- if (auto ssl = Ssl::CreateServer(sslContext, conn->fd, "client https start")) {
+ if (Ssl::CreateServer(sslContext, conn, "client https start")) {
debugs(33, 5, "will negotate SSL on " << conn);
- return ssl;
+ return true;
}
conn->close();
- return nullptr;
+ return false;
}
/**
Squid_SSL_accept(ConnStateData *conn, PF *callback)
{
int fd = conn->clientConnection->fd;
- auto ssl = fd_table[fd].ssl;
+ auto ssl = fd_table[fd].ssl.get();
int ret;
errno = 0;
switch (ssl_error) {
case SSL_ERROR_WANT_READ:
- Comm::SetSelect(fd, COMM_SELECT_READ, callback, conn, 0);
+ Comm::SetSelect(fd, COMM_SELECT_READ, callback, (callback != NULL ? conn : NULL), 0);
return 0;
case SSL_ERROR_WANT_WRITE:
- Comm::SetSelect(fd, COMM_SELECT_WRITE, callback, conn, 0);
+ Comm::SetSelect(fd, COMM_SELECT_WRITE, callback, (callback != NULL ? conn : NULL), 0);
return 0;
case SSL_ERROR_SYSCALL:
{
ConnStateData *conn = (ConnStateData *)data;
X509 *client_cert;
- auto ssl = fd_table[fd].ssl;
+ auto ssl = fd_table[fd].ssl.get();
int ret;
if ((ret = Squid_SSL_accept(conn, clientNegotiateSSL)) <= 0) {
return;
}
- if (SSL_session_reused(ssl)) {
+ if (Security::SessionIsResumed(fd_table[fd].ssl)) {
debugs(83, 2, "clientNegotiateSSL: Session " << SSL_get_session(ssl) <<
" reused on FD " << fd << " (" << fd_table[fd].ipaddr << ":" << (int)fd_table[fd].remote_port << ")");
} else {
- if (do_debug(83, 4)) {
+ if (Debug::Enabled(83, 4)) {
/* Write out the SSL session details.. actually the call below, but
* OpenSSL headers do strange typecasts confusing GCC.. */
/* PEM_write_SSL_SESSION(debug_log, SSL_get_session(ssl)); */
")");
}
- debugs(83, 3, "clientNegotiateSSL: FD " << fd << " negotiated cipher " <<
- SSL_get_cipher(ssl));
+ // Connection established. Retrieve TLS connection parameters for logging.
+ conn->clientConnection->tlsNegotiations()->retrieveNegotiatedInfo(ssl);
client_cert = SSL_get_peer_certificate(ssl);
}
/**
- * If Security::ContextPointer is given, starts reading the TLS handshake.
- * Otherwise, calls switchToHttps to generate a dynamic Security::ContextPointer.
+ * If Security::ContextPtr is given, starts reading the TLS handshake.
+ * Otherwise, calls switchToHttps to generate a dynamic Security::ContextPtr.
*/
static void
-httpsEstablish(ConnStateData *connState, Security::ContextPointer sslContext)
+httpsEstablish(ConnStateData *connState, Security::ContextPtr sslContext)
{
- Security::SessionPointer ssl = nullptr;
assert(connState);
const Comm::ConnectionPointer &details = connState->clientConnection;
- if (!sslContext || !(ssl = httpsCreate(details, sslContext)))
+ if (!sslContext || !httpsCreate(details, sslContext))
return;
typedef CommCbMemFunT<ConnStateData, CommTimeoutCbParams> TimeoutDialer;
debugs(33, 2, HERE << "sslBump not needed for " << connState->clientConnection);
connState->sslBumpMode = Ssl::bumpNone;
}
- connState->fakeAConnectRequest("ssl-bump", connState->inBuf);
+ if (!connState->fakeAConnectRequest("ssl-bump", connState->inBuf))
+ connState->clientConnection->close();
}
/** handle a new HTTPS connection */
if (s->tcp_keepalive.enabled) {
commSetTcpKeepalive(params.conn->fd, s->tcp_keepalive.idle, s->tcp_keepalive.interval, s->tcp_keepalive.timeout);
}
-
++incoming_sockets_accepted;
// Socket is ready, setup the connection manager to start using it
- ConnStateData *connState = Https::NewServer(xact);
- AsyncJob::Start(connState); // usually async-calls postHttpsAccept()
+ auto *srv = Https::NewServer(xact);
+ AsyncJob::Start(srv); // usually async-calls postHttpsAccept()
}
void
ACLFilledChecklist *acl_checklist = new ACLFilledChecklist(Config.accessList.ssl_bump, request, NULL);
acl_checklist->src_addr = clientConnection->remote;
acl_checklist->my_addr = port->s;
+ // Build a local AccessLogEntry to allow requiresAle() acls work
+ acl_checklist->al = new AccessLogEntry;
+ acl_checklist->al->cache.start_time = current_time;
+ acl_checklist->al->tcpClient = clientConnection;
+ acl_checklist->al->cache.port = port;
+ acl_checklist->al->cache.caddr = log_addr;
+ HTTPMSGUNLOCK(acl_checklist->al->request);
+ acl_checklist->al->request = request;
+ HTTPMSGLOCK(acl_checklist->al->request);
acl_checklist->nonBlockingCheck(httpsSslBumpAccessCheckDone, this);
return;
} else {
- Security::ContextPointer sslContext = port->staticSslContext.get();
- httpsEstablish(this, sslContext);
+ httpsEstablish(this, port->secure.staticContext.get());
}
}
debugs(33, 5, HERE << "Certificate for " << sslConnectHostOrIp << " was successfully recieved from ssl_crtd");
if (sslServerBump && (sslServerBump->act.step1 == Ssl::bumpPeek || sslServerBump->act.step1 == Ssl::bumpStare)) {
doPeekAndSpliceStep();
- auto ssl = fd_table[clientConnection->fd].ssl;
+ auto ssl = fd_table[clientConnection->fd].ssl.get();
bool ret = Ssl::configureSSLUsingPkeyAndCertFromMemory(ssl, reply_message.getBody().c_str(), *port);
if (!ret)
debugs(33, 5, "Failed to set certificates to ssl object for PeekAndSplice mode");
+
+ SSL_CTX *sslContext = SSL_get_SSL_CTX(ssl);
+ Ssl::configureUnconfiguredSslContext(sslContext, signAlgorithm, *port);
} else {
auto ctx = Ssl::generateSslContextUsingPkeyAndCertFromMemory(reply_message.getBody().c_str(), *port);
getSslContextDone(ctx, true);
ACLFilledChecklist checklist(NULL, sslServerBump->request.getRaw(),
clientConnection != NULL ? clientConnection->rfc931 : dash_str);
- checklist.sslErrors = cbdataReference(sslServerBump->sslErrors);
+ checklist.sslErrors = cbdataReference(sslServerBump->sslErrors());
for (sslproxy_cert_adapt *ca = Config.ssl_client.cert_adapt; ca != NULL; ca = ca->next) {
// If the algorithm already set, then ignore it.
void
ConnStateData::getSslContextStart()
{
- // XXX starting SSL with a pipeline of requests still waiting for non-SSL replies?
- assert(pipeline.count() < 2); // the CONNECT is okay for now. Anything else is a bug.
- pipeline.terminateAll(0);
- /* careful: terminateAll(0) above frees request, host, etc. */
+ // If we are called, then CONNECT has succeeded. Finalize it.
+ if (auto xact = pipeline.front()) {
+ if (xact->http && xact->http->request && xact->http->request->method == Http::METHOD_CONNECT)
+ xact->finished();
+ // cannot proceed with encryption if requests wait for plain responses
+ Must(pipeline.empty());
+ }
+ /* careful: finished() above frees request, host, etc. */
if (port->generateHostCertificates) {
Ssl::CertificateProperties certProperties;
if (!(sslServerBump && (sslServerBump->act.step1 == Ssl::bumpPeek || sslServerBump->act.step1 == Ssl::bumpStare))) {
debugs(33, 5, "Finding SSL certificate for " << sslBumpCertKey << " in cache");
Ssl::LocalContextStorage * ssl_ctx_cache = Ssl::TheGlobalContextStorage.getLocalStorage(port->s);
- Security::ContextPointer dynCtx = nullptr;
- Ssl::SSL_CTX_Pointer *cachedCtx = ssl_ctx_cache ? ssl_ctx_cache->get(sslBumpCertKey.termedBuf()) : NULL;
+ Security::ContextPtr dynCtx = nullptr;
+ Security::ContextPointer *cachedCtx = ssl_ctx_cache ? ssl_ctx_cache->get(sslBumpCertKey.termedBuf()) : nullptr;
if (cachedCtx && (dynCtx = cachedCtx->get())) {
debugs(33, 5, "SSL certificate for " << sslBumpCertKey << " found in cache");
if (Ssl::verifySslCertificate(dynCtx, certProperties)) {
debugs(33, 5, HERE << "Generating SSL certificate for " << certProperties.commonName);
if (sslServerBump && (sslServerBump->act.step1 == Ssl::bumpPeek || sslServerBump->act.step1 == Ssl::bumpStare)) {
doPeekAndSpliceStep();
- auto ssl = fd_table[clientConnection->fd].ssl;
+ auto ssl = fd_table[clientConnection->fd].ssl.get();
if (!Ssl::configureSSL(ssl, certProperties, *port))
debugs(33, 5, "Failed to set certificates to ssl object for PeekAndSplice mode");
+
+ SSL_CTX *sslContext = SSL_get_SSL_CTX(ssl);
+ Ssl::configureUnconfiguredSslContext(sslContext, certProperties.signAlgorithm, *port);
} else {
auto dynCtx = Ssl::generateSslContext(certProperties, *port);
getSslContextDone(dynCtx, true);
}
void
-ConnStateData::getSslContextDone(Security::ContextPointer sslContext, bool isNew)
+ConnStateData::getSslContextDone(Security::ContextPtr sslContext, bool isNew)
{
// Try to add generated ssl context to storage.
if (port->generateHostCertificates && isNew) {
- 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());
+ if (sslContext && (signAlgorithm == Ssl::algSignTrusted)) {
+ Ssl::chainCertificatesToSSLContext(sslContext, *port);
+ } else if (signAlgorithm == Ssl::algSignTrusted) {
+ debugs(33, DBG_IMPORTANT, "WARNING: can not add signing certificate to SSL context chain because SSL context chain is invalid!");
}
//else it is self-signed or untrusted do not attrach any certificate
Ssl::LocalContextStorage *ssl_ctx_cache = Ssl::TheGlobalContextStorage.getLocalStorage(port->s);
assert(sslBumpCertKey.size() > 0 && sslBumpCertKey[0] != '\0');
if (sslContext) {
- if (!ssl_ctx_cache || !ssl_ctx_cache->add(sslBumpCertKey.termedBuf(), new Ssl::SSL_CTX_Pointer(sslContext))) {
+ if (!ssl_ctx_cache || !ssl_ctx_cache->add(sslBumpCertKey.termedBuf(), new Security::ContextPointer(sslContext))) {
// If it is not in storage delete after using. Else storage deleted it.
fd_table[clientConnection->fd].dynamicSslContext = sslContext;
}
// If generated ssl context = NULL, try to use static ssl context.
if (!sslContext) {
- if (!port->staticSslContext) {
- debugs(83, DBG_IMPORTANT, "Closing SSL " << clientConnection->remote << " as lacking SSL context");
+ if (!port->secure.staticContext) {
+ debugs(83, DBG_IMPORTANT, "Closing " << clientConnection->remote << " as lacking TLS context");
clientConnection->close();
return;
} else {
- debugs(33, 5, HERE << "Using static ssl context.");
- sslContext = port->staticSslContext.get();
+ debugs(33, 5, "Using static TLS context.");
+ sslContext = port->secure.staticContext.get();
}
}
this, ConnStateData::requestTimeout);
commSetConnTimeout(clientConnection, Config.Timeout.request, timeoutCall);
- // Disable the client read handler until CachePeer selection is complete
- Comm::SetSelect(clientConnection->fd, COMM_SELECT_READ, NULL, NULL, 0);
- Comm::SetSelect(clientConnection->fd, COMM_SELECT_READ, clientNegotiateSSL, this, 0);
switchedToHttps_ = true;
+
+ auto ssl = fd_table[clientConnection->fd].ssl.get();
+ BIO *b = SSL_get_rbio(ssl);
+ Ssl::ClientBio *bio = static_cast<Ssl::ClientBio *>(b->ptr);
+ bio->setReadBufData(inBuf);
+ inBuf.clear();
+ clientNegotiateSSL(clientConnection->fd, this);
}
void
if (bumpServerMode == Ssl::bumpServerFirst && !sslServerBump) {
request->flags.sslPeek = true;
sslServerBump = new Ssl::ServerBump(request);
-
- // will call httpsPeeked() with certificate and connection, eventually
- FwdState::fwdStart(clientConnection, sslServerBump->entry, sslServerBump->request.getRaw());
- return;
} else if (bumpServerMode == Ssl::bumpPeek || bumpServerMode == Ssl::bumpStare) {
request->flags.sslPeek = true;
sslServerBump = new Ssl::ServerBump(request, NULL, bumpServerMode);
- startPeekAndSplice();
- return;
}
- // otherwise, use sslConnectHostOrIp
- getSslContextStart();
+ // commSetConnTimeout() was called for this request before we switched.
+ // Fix timeout to request_start_timeout
+ typedef CommCbMemFunT<ConnStateData, CommTimeoutCbParams> TimeoutDialer;
+ AsyncCall::Pointer timeoutCall = JobCallback(33, 5,
+ TimeoutDialer, this, ConnStateData::requestTimeout);
+ commSetConnTimeout(clientConnection, Config.Timeout.request_start_timeout, timeoutCall);
+ // Also reset receivedFirstByte_ flag to allow this timeout work in the case we have
+ // a bumbed "connect" request on non transparent port.
+ receivedFirstByte_ = false;
+ // Get more data to peek at Tls
+ parsingTlsHandshake = true;
+ readSomeData();
+}
+
+void
+ConnStateData::parseTlsHandshake()
+{
+ Must(parsingTlsHandshake);
+
+ assert(!inBuf.isEmpty());
+ receivedFirstByte();
+ fd_note(clientConnection->fd, "Parsing TLS handshake");
+
+ bool unsupportedProtocol = false;
+ try {
+ if (!tlsParser.parseHello(inBuf)) {
+ // need more data to finish parsing
+ readSomeData();
+ return;
+ }
+ }
+ catch (const std::exception &ex) {
+ debugs(83, 2, "error on FD " << clientConnection->fd << ": " << ex.what());
+ unsupportedProtocol = true;
+ }
+
+ parsingTlsHandshake = false;
+
+ // Even if the parser failed, each TLS detail should either be set
+ // correctly or still be "unknown"; copying unknown detail is a no-op.
+ clientConnection->tlsNegotiations()->retrieveParsedInfo(tlsParser.details);
+
+ // We should disable read/write handlers
+ Comm::SetSelect(clientConnection->fd, COMM_SELECT_READ, NULL, NULL, 0);
+ Comm::SetSelect(clientConnection->fd, COMM_SELECT_WRITE, NULL, NULL, 0);
+
+ if (!sslServerBump) { // BumpClientFirst mode does not use this member
+ getSslContextStart();
+ return;
+ } else if (sslServerBump->act.step1 == Ssl::bumpServerFirst) {
+ // will call httpsPeeked() with certificate and connection, eventually
+ FwdState::fwdStart(clientConnection, sslServerBump->entry, sslServerBump->request.getRaw());
+ } else {
+ Must(sslServerBump->act.step1 == Ssl::bumpPeek || sslServerBump->act.step1 == Ssl::bumpStare);
+ startPeekAndSplice(unsupportedProtocol);
+ }
}
bool
checklist.conn(this);
allow_t answer = checklist.fastCheck();
if (answer == ACCESS_ALLOWED && answer.kind == 1) {
- splice();
- return true;
+ return splice();
}
}
return false;
}
-/** negotiate an SSL connection */
-static void
-clientPeekAndSpliceSSL(int fd, void *data)
+void
+ConnStateData::startPeekAndSplice(const bool unsupportedProtocol)
{
- ConnStateData *conn = (ConnStateData *)data;
- auto ssl = fd_table[fd].ssl;
-
- debugs(83, 5, "Start peek and splice on FD " << fd);
-
- int ret = 0;
- if ((ret = Squid_SSL_accept(conn, clientPeekAndSpliceSSL)) < 0)
- debugs(83, 2, "SSL_accept failed.");
-
- BIO *b = SSL_get_rbio(ssl);
- assert(b);
- Ssl::ClientBio *bio = static_cast<Ssl::ClientBio *>(b->ptr);
- if (ret < 0) {
- const err_type err = bio->noSslClient() ? ERR_PROTOCOL_UNKNOWN : ERR_SECURE_ACCEPT_FAIL;
- if (!conn->spliceOnError(err))
- conn->clientConnection->close();
+ if (unsupportedProtocol) {
+ if (!spliceOnError(ERR_PROTOCOL_UNKNOWN))
+ clientConnection->close();
return;
}
- if (bio->rBufData().contentSize() > 0)
- conn->receivedFirstByte();
-
- if (bio->gotHello()) {
- if (conn->serverBump()) {
- Ssl::Bio::sslFeatures const &features = bio->getFeatures();
- if (!features.serverName.isEmpty()) {
- conn->serverBump()->clientSni = features.serverName;
- conn->resetSslCommonName(features.serverName.c_str());
- }
+ if (serverBump()) {
+ Security::TlsDetails::Pointer const &details = tlsParser.details;
+ if (details && !details->serverName.isEmpty()) {
+ serverBump()->clientSni = details->serverName;
+ resetSslCommonName(details->serverName.c_str());
}
-
- debugs(83, 5, "I got hello. Start forwarding the request!!! ");
- Comm::SetSelect(fd, COMM_SELECT_READ, NULL, NULL, 0);
- Comm::SetSelect(fd, COMM_SELECT_WRITE, NULL, NULL, 0);
- conn->startPeekAndSpliceDone();
- return;
}
-}
-
-void ConnStateData::startPeekAndSplice()
-{
- // will call httpsPeeked() with certificate and connection, eventually
- auto unConfiguredCTX = Ssl::createSSLContext(port->signingCert, port->signPkey, *port);
- fd_table[clientConnection->fd].dynamicSslContext = unConfiguredCTX;
-
- if (!httpsCreate(clientConnection, unConfiguredCTX))
- return;
-
- // commSetConnTimeout() was called for this request before we switched.
- // Fix timeout to request_start_timeout
- typedef CommCbMemFunT<ConnStateData, CommTimeoutCbParams> TimeoutDialer;
- AsyncCall::Pointer timeoutCall = JobCallback(33, 5,
- TimeoutDialer, this, ConnStateData::requestTimeout);
- commSetConnTimeout(clientConnection, Config.Timeout.request_start_timeout, timeoutCall);
- // Also reset receivedFirstByte_ flag to allow this timeout work in the case we have
- // a bumbed "connect" request on non transparent port.
- receivedFirstByte_ = false;
-
- // Disable the client read handler until CachePeer selection is complete
- Comm::SetSelect(clientConnection->fd, COMM_SELECT_READ, NULL, NULL, 0);
- Comm::SetSelect(clientConnection->fd, COMM_SELECT_READ, clientPeekAndSpliceSSL, this, 0);
- switchedToHttps_ = true;
- auto ssl = fd_table[clientConnection->fd].ssl;
- BIO *b = SSL_get_rbio(ssl);
- Ssl::ClientBio *bio = static_cast<Ssl::ClientBio *>(b->ptr);
- bio->hold(true);
+ startPeekAndSpliceDone();
}
void httpsSslBumpStep2AccessCheckDone(allow_t answer, void *data)
connState->clientConnection->close();
} else if (bumpAction != Ssl::bumpSplice) {
connState->startPeekAndSpliceDone();
- } else
- connState->splice();
+ } else if (!connState->splice())
+ connState->clientConnection->close();
}
-void
+bool
ConnStateData::splice()
{
- //Normally we can splice here, because we just got client hello message
- auto ssl = fd_table[clientConnection->fd].ssl;
- BIO *b = SSL_get_rbio(ssl);
- Ssl::ClientBio *bio = static_cast<Ssl::ClientBio *>(b->ptr);
- MemBuf const &rbuf = bio->rBufData();
- debugs(83,5, "Bio for " << clientConnection << " read " << rbuf.contentSize() << " helo bytes");
- // Do splice:
- fd_table[clientConnection->fd].read_method = &default_read_method;
- fd_table[clientConnection->fd].write_method = &default_write_method;
+ // normally we can splice here, because we just got client hello message
+
+ if (fd_table[clientConnection->fd].ssl.get()) {
+ // Restore default read methods
+ fd_table[clientConnection->fd].read_method = &default_read_method;
+ fd_table[clientConnection->fd].write_method = &default_write_method;
+ }
if (transparent()) {
// set the current protocol to something sensible (was "HTTPS" for the bumping process)
// we are sending a faked-up HTTP/1.1 message wrapper, so go with that.
transferProtocol = Http::ProtocolVersion();
- // XXX: copy from MemBuf reallocates, not a regression since old code did too
- SBuf temp;
- temp.append(rbuf.content(), rbuf.contentSize());
- fakeAConnectRequest("intercepted TLS spliced", temp);
+ return fakeAConnectRequest("intercepted TLS spliced", inBuf);
} else {
// XXX: assuming that there was an HTTP/1.1 CONNECT to begin with...
// reset the current protocol to HTTP/1.1 (was "HTTPS" for the bumping process)
transferProtocol = Http::ProtocolVersion();
- // inBuf still has the "CONNECT ..." request data, reset it to SSL hello message
- inBuf.append(rbuf.content(), rbuf.contentSize());
- ClientSocketContext::Pointer context = getCurrentContext();
+ Http::StreamPointer context = pipeline.front();
ClientHttpRequest *http = context->http;
tunnelStart(http);
+ return true;
}
}
{
// This is the Step2 of the SSL bumping
assert(sslServerBump);
+ Http::StreamPointer context = pipeline.front();
+ ClientHttpRequest *http = context ? context->http : NULL;
+
if (sslServerBump->step == Ssl::bumpStep1) {
sslServerBump->step = Ssl::bumpStep2;
// Run a accessList check to check if want to splice or continue bumping
ACLFilledChecklist *acl_checklist = new ACLFilledChecklist(Config.accessList.ssl_bump, sslServerBump->request.getRaw(), NULL);
+ acl_checklist->al = http ? http->al : NULL;
//acl_checklist->src_addr = params.conn->remote;
//acl_checklist->my_addr = s->s;
acl_checklist->banAction(allow_t(ACCESS_ALLOWED, Ssl::bumpNone));
return;
}
- FwdState::fwdStart(clientConnection, sslServerBump->entry, sslServerBump->request.getRaw());
+ // will call httpsPeeked() with certificate and connection, eventually
+ auto unConfiguredCTX = Ssl::createSSLContext(port->signingCert, port->signPkey, *port);
+ fd_table[clientConnection->fd].dynamicSslContext = unConfiguredCTX;
+
+ if (!httpsCreate(clientConnection, unConfiguredCTX))
+ return;
+
+ switchedToHttps_ = true;
+
+ auto ssl = fd_table[clientConnection->fd].ssl.get();
+ BIO *b = SSL_get_rbio(ssl);
+ Ssl::ClientBio *bio = static_cast<Ssl::ClientBio *>(b->ptr);
+ bio->setReadBufData(inBuf);
+ bio->hold(true);
+
+ // Here squid should have all of the client hello message so the
+ // Squid_SSL_accept should return 0;
+ // This block exist only to force openSSL parse client hello and detect
+ // ERR_SECURE_ACCEPT_FAIL error, which should be checked and splice if required.
+ int ret = 0;
+ if ((ret = Squid_SSL_accept(this, NULL)) < 0) {
+ debugs(83, 2, "SSL_accept failed.");
+ const err_type err = ERR_SECURE_ACCEPT_FAIL;
+ if (!spliceOnError(err))
+ clientConnection->close();
+ return;
+ }
+
+ // We need to reset inBuf here, to be used by incoming requests in the case
+ // of SSL bump
+ inBuf.clear();
+
+ debugs(83, 5, "Peek and splice at step2 done. Start forwarding the request!!! ");
+ FwdState::Start(clientConnection, sslServerBump->entry, sslServerBump->request.getRaw(), http ? http->al : NULL);
}
void
ConnStateData::doPeekAndSpliceStep()
{
- auto ssl = fd_table[clientConnection->fd].ssl;
+ auto ssl = fd_table[clientConnection->fd].ssl.get();
BIO *b = SSL_get_rbio(ssl);
assert(b);
Ssl::ClientBio *bio = static_cast<Ssl::ClientBio *>(b->ptr);
debugs(33, 5, HERE << "Error while bumping: " << sslConnectHostOrIp);
// copy error detail from bump-server-first request to CONNECT request
- if (currentobject != NULL && currentobject->http != NULL && currentobject->http->request)
- currentobject->http->request->detailError(sslServerBump->request->errType, sslServerBump->request->errDetail);
+ if (!pipeline.empty() && pipeline.front()->http != nullptr && pipeline.front()->http->request)
+ pipeline.front()->http->request->detailError(sslServerBump->request->errType, sslServerBump->request->errDetail);
}
getSslContextStart();
#endif /* USE_OPENSSL */
-void
+bool
ConnStateData::fakeAConnectRequest(const char *reason, const SBuf &payload)
{
// fake a CONNECT request to force connState to tunnel
if (!ret) {
debugs(33, 2, "Failed to start fake CONNECT request for " << reason << " connection: " << clientConnection);
- clientConnection->close();
+ return false;
}
+ return true;
}
/// check FD after clientHttp[s]ConnectionOpened, adjust HttpSockets as needed
clientHttpConnectionsOpen(void)
{
for (AnyP::PortCfgPointer s = HttpPortList; s != NULL; s = s->next) {
+ const SBuf &scheme = AnyP::UriScheme(s->transport.protocol).image();
+
if (MAXTCPLISTENPORTS == NHttpSockets) {
- debugs(1, DBG_IMPORTANT, "WARNING: You have too many 'http_port' lines.");
+ debugs(1, DBG_IMPORTANT, "WARNING: You have too many '" << scheme << "_port' lines.");
debugs(1, DBG_IMPORTANT, " The limit is " << MAXTCPLISTENPORTS << " HTTP ports.");
continue;
}
#if USE_OPENSSL
- if (s->flags.tunnelSslBumping && !Config.accessList.ssl_bump) {
- debugs(33, DBG_IMPORTANT, "WARNING: No ssl_bump configured. Disabling ssl-bump on " << AnyP::UriScheme(s->transport.protocol) << "_port " << s->s);
- s->flags.tunnelSslBumping = false;
+ if (s->flags.tunnelSslBumping) {
+ if (!Config.accessList.ssl_bump) {
+ debugs(33, DBG_IMPORTANT, "WARNING: No ssl_bump configured. Disabling ssl-bump on " << scheme << "_port " << s->s);
+ s->flags.tunnelSslBumping = false;
+ }
+ if (!s->secure.staticContext && !s->generateHostCertificates) {
+ debugs(1, DBG_IMPORTANT, "Will not bump SSL at " << scheme << "_port " << s->s << " due to TLS initialization failure.");
+ s->flags.tunnelSslBumping = false;
+ if (s->transport.protocol == AnyP::PROTO_HTTP)
+ s->secure.encryptTransport = false;
+ }
+ if (s->flags.tunnelSslBumping) {
+ // Create ssl_ctx cache for this port.
+ auto sz = s->dynamicCertMemCacheSize == std::numeric_limits<size_t>::max() ? 4194304 : s->dynamicCertMemCacheSize;
+ Ssl::TheGlobalContextStorage.addLocalStorage(s->s, sz);
+ }
}
- 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->flags.tunnelSslBumping = false;
- }
- 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);
+ if (s->secure.encryptTransport && !s->secure.staticContext) {
+ debugs(1, DBG_CRITICAL, "ERROR: Ignoring " << scheme << "_port " << s->s << " due to TLS context initialization failure.");
+ continue;
}
#endif
// 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->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;
- RefCount<AcceptCall> subCall = commCbCall(5, 5, "httpAccept", CommAcceptCbPtrFun(httpAccept, CommAcceptCbParams(NULL)));
- Subscription::Pointer sub = new CallSubscription<AcceptCall>(subCall);
+ s->listenConn->flags = COMM_NONBLOCKING | (s->flags.tproxyIntercept ? COMM_TRANSPARENT : 0) |
+ (s->flags.natIntercept ? COMM_INTERCEPTION : 0);
- AsyncCall::Pointer listenCall = asyncCall(33,2, "clientListenerConnectionOpened",
- ListeningStartedDialer(&clientListenerConnectionOpened, s, Ipc::fdnHttpSocket, sub));
- Ipc::StartListening(SOCK_STREAM, IPPROTO_TCP, s->listenConn, Ipc::fdnHttpSocket, listenCall);
+ typedef CommCbFunPtrCallT<CommAcceptCbPtrFun> AcceptCall;
+ if (s->transport.protocol == AnyP::PROTO_HTTP) {
+ // setup the subscriptions such that new connections accepted by listenConn are handled by HTTP
+ RefCount<AcceptCall> subCall = commCbCall(5, 5, "httpAccept", CommAcceptCbPtrFun(httpAccept, CommAcceptCbParams(NULL)));
+ Subscription::Pointer sub = new CallSubscription<AcceptCall>(subCall);
- HttpSockets[NHttpSockets] = -1; // set in clientListenerConnectionOpened
- ++NHttpSockets;
- }
-}
+ AsyncCall::Pointer listenCall = asyncCall(33,2, "clientListenerConnectionOpened",
+ ListeningStartedDialer(&clientListenerConnectionOpened, s, Ipc::fdnHttpSocket, sub));
+ Ipc::StartListening(SOCK_STREAM, IPPROTO_TCP, s->listenConn, Ipc::fdnHttpSocket, listenCall);
#if USE_OPENSSL
-static void
-clientHttpsConnectionsOpen(void)
-{
- for (AnyP::PortCfgPointer s = HttpsPortList; s != NULL; s = s->next) {
- if (MAXTCPLISTENPORTS == NHttpSockets) {
- debugs(1, DBG_IMPORTANT, "Ignoring 'https_port' lines exceeding the limit.");
- debugs(1, DBG_IMPORTANT, "The limit is " << MAXTCPLISTENPORTS << " HTTPS ports.");
- continue;
- }
-
- if (!s->staticSslContext) {
- debugs(1, DBG_IMPORTANT, "Ignoring https_port " << s->s <<
- " due to SSL initialization failure.");
- continue;
- }
-
- // 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 " << AnyP::UriScheme(s->transport.protocol) << "_port " << s->s);
- s->flags.tunnelSslBumping = false;
- }
-
- if (s->flags.tunnelSslBumping && !s->staticSslContext && !s->generateHostCertificates) {
- debugs(1, DBG_IMPORTANT, "Will not bump SSL at https_port " << s->s << " due to SSL initialization failure.");
- s->flags.tunnelSslBumping = false;
- }
-
- 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);
+ } else if (s->transport.protocol == AnyP::PROTO_HTTPS) {
+ // setup the subscriptions such that new connections accepted by listenConn are handled by HTTPS
+ RefCount<AcceptCall> subCall = commCbCall(5, 5, "httpsAccept", CommAcceptCbPtrFun(httpsAccept, CommAcceptCbParams(NULL)));
+ Subscription::Pointer sub = new CallSubscription<AcceptCall>(subCall);
+
+ AsyncCall::Pointer listenCall = asyncCall(33, 2, "clientListenerConnectionOpened",
+ ListeningStartedDialer(&clientListenerConnectionOpened,
+ s, Ipc::fdnHttpsSocket, sub));
+ Ipc::StartListening(SOCK_STREAM, IPPROTO_TCP, s->listenConn, Ipc::fdnHttpsSocket, listenCall);
+#endif
}
- // 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->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;
- RefCount<AcceptCall> subCall = commCbCall(5, 5, "httpsAccept", CommAcceptCbPtrFun(httpsAccept, CommAcceptCbParams(NULL)));
- Subscription::Pointer sub = new CallSubscription<AcceptCall>(subCall);
-
- AsyncCall::Pointer listenCall = asyncCall(33, 2, "clientListenerConnectionOpened",
- ListeningStartedDialer(&clientListenerConnectionOpened,
- s, Ipc::fdnHttpsSocket, sub));
- Ipc::StartListening(SOCK_STREAM, IPPROTO_TCP, s->listenConn, Ipc::fdnHttpsSocket, listenCall);
- HttpSockets[NHttpSockets] = -1;
+ HttpSockets[NHttpSockets] = -1; // set in clientListenerConnectionOpened
++NHttpSockets;
}
}
-#endif
void
clientStartListeningOn(AnyP::PortCfgPointer &port, const RefCount< CommCbFunPtrCallT<CommAcceptCbPtrFun> > &subCall, const Ipc::FdNoteId fdNote)
clientOpenListenSockets(void)
{
clientHttpConnectionsOpen();
-#if USE_OPENSSL
- clientHttpsConnectionsOpen();
-#endif
Ftp::StartListening();
if (NHttpSockets < 1)
{
for (AnyP::PortCfgPointer s = HttpPortList; s != NULL; s = s->next) {
if (s->listenConn != NULL) {
- debugs(1, DBG_IMPORTANT, "Closing HTTP port " << s->listenConn->local);
- s->listenConn->close();
- s->listenConn = NULL;
- }
- }
-
-#if USE_OPENSSL
- for (AnyP::PortCfgPointer s = HttpsPortList; s != NULL; s = s->next) {
- if (s->listenConn != NULL) {
- debugs(1, DBG_IMPORTANT, "Closing HTTPS port " << s->listenConn->local);
+ debugs(1, DBG_IMPORTANT, "Closing HTTP(S) port " << s->listenConn->local);
s->listenConn->close();
s->listenConn = NULL;
}
}
-#endif
Ftp::StopListening();
int
varyEvaluateMatch(StoreEntry * entry, HttpRequest * request)
{
- const char *vary = request->vary_headers;
+ SBuf vary(request->vary_headers);
int has_vary = entry->getReply()->header.has(Http::HdrType::VARY);
#if X_ACCELERATOR_VARY
entry->getReply()->header.has(Http::HdrType::HDR_X_ACCELERATOR_VARY);
#endif
- if (!has_vary || !entry->mem_obj->vary_headers) {
- if (vary) {
+ if (!has_vary || entry->mem_obj->vary_headers.isEmpty()) {
+ if (!vary.isEmpty()) {
/* Oops... something odd is going on here.. */
debugs(33, DBG_IMPORTANT, "varyEvaluateMatch: Oops. Not a Vary object on second attempt, '" <<
entry->mem_obj->urlXXX() << "' '" << vary << "'");
- safe_free(request->vary_headers);
+ request->vary_headers.clear();
return VARY_CANCEL;
}
*/
vary = httpMakeVaryMark(request, entry->getReply());
- if (vary) {
- request->vary_headers = xstrdup(vary);
+ if (!vary.isEmpty()) {
+ request->vary_headers = vary;
return VARY_OTHER;
} else {
/* Ouch.. we cannot handle this kind of variance */
return VARY_CANCEL;
}
} else {
- if (!vary) {
+ if (vary.isEmpty()) {
vary = httpMakeVaryMark(request, entry->getReply());
- if (vary)
- request->vary_headers = xstrdup(vary);
+ if (!vary.isEmpty())
+ request->vary_headers = vary;
}
- if (!vary) {
+ if (vary.isEmpty()) {
/* Ouch.. we cannot handle this kind of variance */
/* XXX This cannot really happen, but just to be complete */
return VARY_CANCEL;
- } else if (strcmp(vary, entry->mem_obj->vary_headers) == 0) {
+ } else if (vary.cmp(entry->mem_obj->vary_headers) == 0) {
return VARY_MATCH;
} else {
/* Oops.. we have already been here and still haven't
Must(!bodyPipe); // we rely on it being nil after we are done with body
if (withSuccess) {
Must(myPipe->bodySizeKnown());
- ClientSocketContext::Pointer context = getCurrentContext();
+ Http::StreamPointer context = pipeline.front();
if (context != NULL && context->http && context->http->request)
context->http->request->setContentLength(myPipe->bodySize());
}
bodyParser = NULL;
}
+// XXX: this is an HTTP/1-only operation
void
ConnStateData::sendControlMsg(HttpControlMsg msg)
{
return;
}
- ClientSocketContext::Pointer context = getCurrentContext();
- if (context != NULL) {
- context->writeControlMsg(msg); // will call msg.cbSuccess
+ // HTTP/1 1xx status messages are only valid when there is a transaction to trigger them
+ if (!pipeline.empty()) {
+ HttpReply::Pointer rep(msg.reply);
+ Must(rep);
+ // remember the callback
+ cbControlMsgSent = msg.cbSuccess;
+
+ typedef CommCbMemFunT<HttpControlMsgSink, CommIoCbParams> Dialer;
+ AsyncCall::Pointer call = JobCallback(33, 5, Dialer, this, HttpControlMsgSink::wroteControlMsg);
+
+ writeControlMsgAndCall(rep.getRaw(), call);
return;
}
// renegotiations. We should close the connection except for the last case.
Must(pinning.serverConnection != nullptr);
- SSL *ssl = fd_table[pinning.serverConnection->fd].ssl;
+ auto ssl = fd_table[pinning.serverConnection->fd].ssl.get();
if (!ssl)
return false;
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.
+ // ConnStateData::kick() 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();
* connection has gone away */
}
+void
+ConnStateData::checkLogging()
+{
+ // if we are parsing request body, its request is responsible for logging
+ if (bodyPipe)
+ return;
+
+ // a request currently using this connection is responsible for logging
+ if (!pipeline.empty() && pipeline.back()->mayUseConnection())
+ return;
+
+ /* Either we are waiting for the very first transaction, or
+ * we are done with the Nth transaction and are waiting for N+1st.
+ * XXX: We assume that if anything was added to inBuf, then it could
+ * only be consumed by actions already covered by the above checks.
+ */
+
+ // do not log connections that closed after a transaction (it is normal)
+ // TODO: access_log needs ACLs to match received-no-bytes connections
+ // XXX: TLS may return here even though we got no transactions yet
+ // XXX: PROXY protocol may return here even though we got no
+ // transactions yet
+ if (receivedFirstByte_ && inBuf.isEmpty())
+ return;
+
+ /* Create a temporary ClientHttpRequest object. Its destructor will log. */
+ ClientHttpRequest http(this);
+ http.req_sz = inBuf.length();
+ char const *uri = "error:transaction-end-before-headers";
+ http.uri = xstrdup(uri);
+ setLogUri(&http, uri);
+}
+