Hi all,
This patch fixes the bug 1632
(http://www.squid-cache.org/bugs/show_bug.cgi?id=1632)
It is based on the original squid2.5 connection pinning patch developed
by Henrik (http://devel.squid-cache.org/projects.html#pinning) and the
related squid 2.6 connection pinning code.
Although I spend many hours looking on pined connections I am still not
absolutely sure that does not have bugs. However the code is very
similar with this in squid2.6 (where the pinning code runs for years)
and I hope will be easy to fix problems and bugs.
Regards,
Christos
# Bazaar merge directive format 2 (Bazaar 0.90)
# revision_id: [EMAIL PROTECTED]
# 2khd7117cp125tnf
# target_branch: file:///home/projects/squid/squid3-bzr/squid-\
# repo/trunk/
# testament_sha1: c79755cca30b877e873083896f61ad63a641ae88
# timestamp: 2008-09-20 20:27:37 +0300
# base_revision_id: [EMAIL PROTECTED]
# h7e6i88s7t5lieme
#
# Begin patch
=== modified file 'src/HttpHeader.cc'
--- src/HttpHeader.cc 2008-07-13 08:37:43 +0000
+++ src/HttpHeader.cc 2008-07-17 18:33:07 +0000
@@ -112,6 +112,7 @@
{"Proxy-Authentication-Info", HDR_PROXY_AUTHENTICATION_INFO, ftStr},
{"Proxy-Authorization", HDR_PROXY_AUTHORIZATION, ftStr},
{"Proxy-Connection", HDR_PROXY_CONNECTION, ftStr},
+ {"Proxy-support", HDR_PROXY_SUPPORT, ftStr},
{"Public", HDR_PUBLIC, ftStr},
{"Range", HDR_RANGE, ftPRange},
{"Referer", HDR_REFERER, ftStr},
@@ -172,6 +173,7 @@
HDR_IF_MATCH, HDR_IF_NONE_MATCH,
HDR_LINK, HDR_PRAGMA,
HDR_PROXY_CONNECTION,
+ HDR_PROXY_SUPPORT,
HDR_TRANSFER_ENCODING,
HDR_UPGRADE,
HDR_VARY,
=== modified file 'src/HttpHeader.h'
--- src/HttpHeader.h 2008-07-13 08:37:43 +0000
+++ src/HttpHeader.h 2008-07-17 18:33:07 +0000
@@ -83,6 +83,7 @@
HDR_PROXY_AUTHENTICATION_INFO,
HDR_PROXY_AUTHORIZATION,
HDR_PROXY_CONNECTION,
+ HDR_PROXY_SUPPORT,
HDR_PUBLIC,
HDR_RANGE,
HDR_REQUEST_RANGE, /**< some clients use this, sigh */
=== modified file 'src/HttpReply.cc'
--- src/HttpReply.cc 2008-07-02 03:49:07 +0000
+++ src/HttpReply.cc 2008-09-02 08:03:35 +0000
@@ -563,5 +563,6 @@
rep->pstate = pstate;
rep->protocol = protocol;
rep->sline = sline;
+ rep->keep_alive = keep_alive;
return rep;
}
=== modified file 'src/HttpRequest.cc'
--- src/HttpRequest.cc 2008-06-07 05:20:05 +0000
+++ src/HttpRequest.cc 2008-06-23 18:02:37 +0000
@@ -74,6 +74,7 @@
login[0] = '\0';
host[0] = '\0';
auth_user_request = NULL;
+ pinned_connection = NULL;
port = 0;
canonical = NULL;
memset(&flags, '\0', sizeof(flags));
@@ -127,6 +128,10 @@
range = NULL;
}
+ if(pinned_connection)
+ cbdataReferenceDone(pinned_connection);
+ pinned_connection = NULL;
+
tag.clean();
extacl_user.clean();
=== modified file 'src/HttpRequest.h'
--- src/HttpRequest.h 2008-06-07 05:20:05 +0000
+++ src/HttpRequest.h 2008-06-23 18:02:37 +0000
@@ -148,6 +148,8 @@
String extacl_log; /* String to be used for access.log purposes */
+ ConnStateData *pinned_connection;
+
#if FOLLOW_X_FORWARDED_FOR
String x_forwarded_for_iterator; /* XXX a list of IP addresses */
#endif /* FOLLOW_X_FORWARDED_FOR */
=== modified file 'src/ICAP/ICAPModXact.cc'
--- src/ICAP/ICAPModXact.cc 2008-09-19 17:26:31 +0000
+++ src/ICAP/ICAPModXact.cc 2008-09-20 15:15:50 +0000
@@ -786,9 +786,12 @@
HttpRequest *newR = new HttpRequest;
inheritVirginProperties(*newR, *oldR);
newHead = newR;
- } else
- if (dynamic_cast<const HttpReply*>(oldHead))
- newHead = new HttpReply;
+ }
+ else if (const HttpReply *oldRep = dynamic_cast<const HttpReply*>(oldHead)) {
+ HttpReply *newRep = new HttpReply;
+ inheritVirginReplyProperties(*newRep, *oldRep);
+ newHead = newRep;
+ }
Must(newHead);
adapted.setHeader(newHead);
@@ -854,6 +857,10 @@
// http://www.squid-cache.org/mail-archive/squid-dev/200703/0040.html
inheritVirginProperties(*newHead, *oldR);
}
+ else if (HttpReply *newRep = dynamic_cast<HttpReply*>(adapted.header)){
+ HttpReply *oldRep = dynamic_cast<HttpReply*>(virgin.header);
+ inheritVirginReplyProperties(*newRep, *oldRep);
+ }
}
decideOnParsingBody();
@@ -895,6 +902,15 @@
newR.auth_user_request = oldR.auth_user_request;
AUTHUSERREQUESTLOCK(newR.auth_user_request, "newR in ICAPModXact");
}
+
+ if(oldR.pinned_connection) {
+ newR.pinned_connection = cbdataReference(oldR.pinned_connection);
+ }
+
+}
+
+void ICAPModXact::inheritVirginReplyProperties(HttpReply &newR, const HttpReply &oldR) {
+ newR.keep_alive = oldR.keep_alive;
}
void ICAPModXact::decideOnParsingBody() {
=== modified file 'src/ICAP/ICAPModXact.h'
--- src/ICAP/ICAPModXact.h 2008-03-31 01:06:13 +0000
+++ src/ICAP/ICAPModXact.h 2008-09-02 08:12:29 +0000
@@ -209,6 +209,7 @@
void parseHttpHead();
bool parseHead(HttpMsg *head);
void inheritVirginProperties(HttpRequest &newR, const HttpRequest &oldR);
+ void inheritVirginReplyProperties(HttpReply &newR, const HttpReply &oldR);
void decideOnParsingBody();
void parseBody();
=== modified file 'src/ProtoPort.h'
--- src/ProtoPort.h 2008-04-09 10:17:28 +0000
+++ src/ProtoPort.h 2008-05-18 20:15:06 +0000
@@ -26,6 +26,7 @@
unsigned int sslBump:1; /**< intercepts CONNECT requests */
int vport; /* virtual port support, -1 for dynamic, >0 static*/
+ int no_connection_auth; /* Don't support connection oriented auth */
int disable_pmtu_discovery;
struct {
=== modified file 'src/cache_cf.cc'
--- src/cache_cf.cc 2008-08-10 05:05:45 +0000
+++ src/cache_cf.cc 2008-09-02 08:32:52 +0000
@@ -1692,6 +1692,7 @@
self_destruct();
p->icp.port = GetUdpService();
+ p->connection_auth = 2; /* auto */
while ((token = strtok(NULL, w_space))) {
if (!strcasecmp(token, "proxy-only")) {
@@ -1829,6 +1830,14 @@
p->front_end_https = 1;
} else if (strcmp(token, "front-end-https=auto") == 0) {
p->front_end_https = 2;
+ }else if (strcmp(token, "connection-auth=off") == 0) {
+ p->connection_auth = 0;
+ } else if (strcmp(token, "connection-auth") == 0) {
+ p->connection_auth = 1;
+ } else if (strcmp(token, "connection-auth=on") == 0) {
+ p->connection_auth = 1;
+ } else if (strcmp(token, "connection-auth=auto") == 0) {
+ p->connection_auth = 2;
} else {
debugs(3, 0, "parse_peer: token='" << token << "'");
self_destruct();
@@ -2936,6 +2945,8 @@
s->accel = 1;
} else if (strcmp(token, "accel") == 0) {
s->accel = 1;
+ } else if (strcmp(token, "no-connection-auth") == 0) {
+ s->no_connection_auth = 1;
} else if (strncmp(token, "disable-pmtu-discovery=", 23) == 0) {
if (!strcasecmp(token + 23, "off"))
s->disable_pmtu_discovery = DISABLE_PMTU_OFF;
@@ -3108,6 +3119,9 @@
if (s->vport)
storeAppendPrintf(e, " vport");
+ if (s->no_connection_auth)
+ storeAppendPrintf(e, " no-connection-auth");
+
if (s->disable_pmtu_discovery != DISABLE_PMTU_OFF) {
const char *pmtu;
=== modified file 'src/cf.data.pre'
--- src/cf.data.pre 2008-09-20 09:43:40 +0000
+++ src/cf.data.pre 2008-09-20 15:15:50 +0000
@@ -1023,6 +1023,10 @@
protocol= Protocol to reconstruct accelerated requests with.
Defaults to http.
+ no-connection-auth
+ Prevent forwarding of Microsoft connection oriented
+ authentication (NTLM, Negotiate and Kerberos)
+
disable-pmtu-discovery=
Control Path-MTU discovery usage:
off lets OS decide on what to do (default).
@@ -1588,6 +1592,7 @@
sslcipher=...
ssloptions=...
front-end-https[=on|auto]
+ connection-auth[=on|off|auto]
use 'proxy-only' to specify objects fetched
from this cache should not be saved locally.
@@ -1796,6 +1801,12 @@
on this header. If set to auto the header will
only be added if the request is forwarded as a https://
URL.
+
+ use connection-auth=off to tell Squid that this peer does
+ not support Microsoft connection oriented authentication,
+ and any such challenges received from there should be
+ ignored. Default is auto to automatically determine the
+ status of the peer.
DOC_END
NAME: cache_peer_domain cache_host_domain
=== modified file 'src/client_side.cc'
--- src/client_side.cc 2008-09-11 04:54:34 +0000
+++ src/client_side.cc 2008-09-14 13:22:13 +0000
@@ -644,6 +644,9 @@
if (!flags.swanSang)
debugs(33, 1, "BUG: ConnStateData was not destroyed properly; FD " << fd);
+ if (pinning.fd >= 0)
+ comm_close(pinning.fd);
+
AUTHUSERREQUESTUNLOCK(auth_user_request, "~conn");
cbdataReferenceDone(port);
@@ -1368,6 +1371,12 @@
debugs(33, 3, "ClientSocketContext::keepaliveNextRequest: FD " << conn->fd);
connIsFinished();
+ if (conn->pinning.pinned && conn->pinning.fd == -1) {
+ debug(33, 2) ("clientKeepaliveNextRequest: FD %d Connection was pinned but server side gone. Terminating client connection\n", conn->fd);
+ comm_close(conn->fd);
+ return;
+ }
+
/** \par
* Attempt to parse a request from the request buffer.
* If we've been fed a pipelined request it may already
@@ -3336,6 +3345,7 @@
ConnStateData::ConnStateData() :AsyncJob("ConnStateData"), transparent_ (false), reading_ (false), closing_ (false)
{
+ pinning.fd = -1;
}
bool
@@ -3415,3 +3425,108 @@
if (allocatedSize)
memFreeBuf(allocatedSize, buf);
}
+
+/* This is a handler normally called by comm_close() */
+static void
+clientPinnedConnectionClosed(int fd, void *data)
+{
+ ConnStateData *conn = (ConnStateData *)data;
+ conn->pinning.fd = -1;
+ if (conn->pinning.peer) {
+ cbdataReferenceDone(conn->pinning.peer);
+ conn->pinning.peer = NULL;
+ }
+ safe_free(conn->pinning.host);
+ /* 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 */
+}
+
+/**
+ * Correlate the current ConnStateData object with the pinning_fd socket descriptor.
+ */
+void ConnStateData::pinConnection(int pinning_fd, HttpRequest *request, struct peer *peer, int auth){
+ fde *f;
+ char desc[FD_DESC_SZ];
+
+ if (pinning.fd == pinning_fd)
+ return;
+ else if (pinning.fd != -1)
+ comm_close(pinning.fd);
+
+ if(pinning.host)
+ safe_free(pinning.host);
+
+ pinning.fd = pinning_fd;
+ pinning.host = xstrdup(request->GetHost());
+ pinning.port = request->port;
+ pinning.pinned = 1;
+ if (pinning.peer)
+ cbdataReferenceDone(pinning.peer);
+ if (peer)
+ pinning.peer = cbdataReference(peer);
+ pinning.auth = auth;
+ f = &fd_table[fd];
+ snprintf(desc, FD_DESC_SZ, "%s pinned connection for %s:%d (%d)",
+ (auth || !peer) ? request->GetHost() : peer->name, f->ipaddr, (int) f->remote_port, fd);
+ fd_note(pinning_fd, desc);
+ comm_add_close_handler(pinning_fd, clientPinnedConnectionClosed, this);
+}
+
+/**
+ * If the current ConnStateData has pinned connection returns the socket descriptor of
+ * pinned connection and the peer object if exists
+*/
+int ConnStateData::getPinnedInfo(const HttpRequest * request, struct peer * &peer){
+
+ if (pinning.fd < 0)
+ return -1;
+
+ if (pinning.auth && request && strcasecmp(pinning.host, request->GetHost()) != 0) {
+ comm_close(pinning.fd);
+ return -1;
+ }
+ if (request && pinning.port != request->port){
+ comm_close(pinning.fd);
+ return -1;
+ }
+ if (pinning.peer && !cbdataReferenceValid(pinning.peer)){
+ comm_close(pinning.fd);
+ return -1;
+ }
+
+ /*
+ Maybe the peer should be a cbdataReference of pinning.peer (and the caller
+ use cbdataReferenceDone).
+ Now the peer is a single pointer...
+ */
+ peer = pinning.peer;
+ return pinning.fd;
+}
+
+
+/**
+ * Returns the pinned socked descriptor if exists and decorrelate the current ConnStateData
+ * object with this socket descriptor.
+ */
+int ConnStateData::getPinnedConnection(const HttpRequest * request, const struct peer * peer, int &auth){
+ int pinning_fd = pinning.fd;
+ if (pinning_fd < 0)
+ return -1;
+
+ if (pinning.auth && request && strcasecmp(pinning.host, request->GetHost()) != 0) {
+ comm_close(pinning_fd);
+ return -1;
+ }
+ auth = pinning.auth;
+ if (peer != pinning.peer){
+ comm_close(pinning_fd);
+ return -1;
+ }
+ cbdataReferenceDone(pinning.peer);
+ pinning.peer = NULL;
+ pinning.fd = -1;
+ comm_remove_close_handler(pinning_fd, clientPinnedConnectionClosed, this);
+ return pinning_fd;
+}
+
+
=== modified file 'src/client_side.h'
--- src/client_side.h 2008-09-11 04:54:34 +0000
+++ src/client_side.h 2008-09-14 13:22:13 +0000
@@ -189,6 +189,15 @@
bool readMoreRequests;
bool swanSang; // XXX: temporary flag to check proper cleanup
} flags;
+ struct {
+ int fd; /* pinned server side connection */
+ char *host; /* host name of pinned connection */
+ int port; /* port of pinned connection */
+ int pinned; /* this connection was pinned */
+ int auth; /* pinned for www authentication */
+ struct peer *peer; /* peer the connection goes via */
+ } pinning;
+
http_port_list *port;
bool transparent() const;
@@ -205,6 +214,9 @@
void handleReadData(char *buf, size_t size);
void handleRequestBodyData();
+ void pinConnection(int fd, HttpRequest *request, struct peer *peer, int auth);
+ int getPinnedInfo(const HttpRequest *request, struct peer * &peer);
+ int getPinnedConnection(const HttpRequest *request, const struct peer *peer, int &auth);
// comm callbacks
void clientReadRequest(const CommIoCbParams &io);
=== modified file 'src/client_side_reply.cc'
--- src/client_side_reply.cc 2008-09-20 05:00:47 +0000
+++ src/client_side_reply.cc 2008-09-20 17:20:16 +0000
@@ -1249,24 +1249,60 @@
/* Filter unproxyable authentication types */
if (http->logType != LOG_TCP_DENIED &&
- (hdr->has(HDR_WWW_AUTHENTICATE) || hdr->has(HDR_PROXY_AUTHENTICATE))) {
+ hdr->has(HDR_WWW_AUTHENTICATE)) {
HttpHeaderPos pos = HttpHeaderInitPos;
HttpHeaderEntry *e;
- int headers_deleted = 0;
+ int connection_auth_blocked = 0;
while ((e = hdr->getEntry(&pos))) {
- if (e->id == HDR_WWW_AUTHENTICATE || e->id == HDR_PROXY_AUTHENTICATE) {
+ if (e->id == HDR_WWW_AUTHENTICATE) {
const char *value = e->value.buf();
if ((strncasecmp(value, "NTLM", 4) == 0 &&
(value[4] == '\0' || value[4] == ' '))
||
(strncasecmp(value, "Negotiate", 9) == 0 &&
- (value[9] == '\0' || value[9] == ' ')))
- hdr->delAt(pos, headers_deleted);
+ (value[9] == '\0' || value[9] == ' '))
+ ||
+ (strncasecmp(value, "Kerberos", 8) == 0 &&
+ (value[8] == '\0' || value[8] == ' ')))
+ {
+ if (request->flags.no_connection_auth) {
+ hdr->delAt(pos, connection_auth_blocked);
+ continue;
+ }
+ request->flags.must_keepalive = 1;
+ if (!request->flags.accelerated && !request->flags.intercepted) {
+ /* The use of the Proxy-Support header is documented in the RFC4559.
+ I was not able to find documentation about the use of the header
+ "Connection: Proxy-Support" and how other http proxies use this header.
+ The only I found is a Henrik's comment:
+
+ "[...] However, this alone is not sufficient. You must also add a
+ "Connection: Proxy-support" header to mark the extension header as a
+ hop-by-hop header to protect from other proxies inbetween this proxy
+ and the client.
+
+ Also, you should also keep in mind that transparently intercepting
+ proxies are quite widely deployed on the Internet today, so to really
+ have a reasonable chance of working the server initiating the
+ "Negotiate" scheme needs to add a extension hop-by-hop header
+ signalling that this connection needs to be kept end-to-end and
+ negotiate (SPNEGO) only accepted by the client if this extension
+ header is seen in the reply. [...]"
+
+ (http://osdir.com/ml/ietf.krb-wg/2002-11/msg00024.html)
+
+ */
+ httpHeaderPutStrf(hdr, HDR_PROXY_SUPPORT, "Session-Based-Authentication");
+ httpHeaderPutStrf(hdr, HDR_CONNECTION, "Proxy-support");
+ }
+ break;
+ }
}
}
- if (headers_deleted)
+
+ if (connection_auth_blocked)
hdr->refreshMask();
}
@@ -1324,6 +1360,12 @@
debugs(88, 3, "clientBuildReplyHeader: Shutting down, don't keep-alive.");
request->flags.proxy_keepalive = 0;
}
+
+ if (request->flags.connection_auth && !reply->keep_alive) {
+ debug(33, 2) ("clientBuildReplyHeader: Connection oriented auth but server side non-persistent\n");
+ request->flags.proxy_keepalive = 0;
+ }
+
/* Append VIA */
if (Config.onoff.via) {
=== modified file 'src/client_side_request.cc'
--- src/client_side_request.cc 2008-09-11 11:14:39 +0000
+++ src/client_side_request.cc 2008-09-14 13:22:13 +0000
@@ -828,6 +828,55 @@
if (req_hdr->has(HDR_AUTHORIZATION))
request->flags.auth = 1;
+
+ if (!request->flags.no_connection_auth) {
+ ConnStateData *http_conn = http->getConn();
+ if (http_conn->pinning.fd != -1) {
+ if (http_conn->pinning.auth) {
+ request->flags.connection_auth = 1;
+ request->flags.auth = 1;
+ } else {
+ request->flags.connection_proxy_auth = 1;
+ }
+ request->pinned_connection = cbdataReference(http_conn);
+ }
+ }
+
+ /* check if connection auth is used, and flag as candidate for pinning
+ * in such case.
+ * Note: we may need to set flags.connection_auth even if the connection
+ * is already pinned if it was pinned earlier due to proxy auth
+ */
+ if (!request->flags.connection_auth) {
+ if (req_hdr->has(HDR_AUTHORIZATION) || req_hdr->has(HDR_PROXY_AUTHORIZATION)) {
+ HttpHeaderPos pos = HttpHeaderInitPos;
+ HttpHeaderEntry *e;
+ int may_pin = 0;
+ while ((e = req_hdr->getEntry(&pos))) {
+ if (e->id == HDR_AUTHORIZATION || e->id == HDR_PROXY_AUTHORIZATION) {
+ const char *value = e->value.buf();
+ if (strncasecmp(value, "NTLM ", 5) == 0
+ ||
+ strncasecmp(value, "Negotiate ", 10) == 0
+ ||
+ strncasecmp(value, "Kerberos ", 9) == 0) {
+ if (e->id == HDR_AUTHORIZATION) {
+ request->flags.connection_auth = 1;
+ may_pin = 1;
+ } else {
+ request->flags.connection_proxy_auth = 1;
+ may_pin = 1;
+ }
+ }
+ }
+ }
+ if (may_pin && !request->pinned_connection) {
+ request->pinned_connection = cbdataReference(http->getConn());
+ }
+ }
+ }
+
+
if (request->login[0] != '\0')
request->flags.auth = 1;
=== modified file 'src/enums.h'
--- src/enums.h 2008-07-11 20:43:43 +0000
+++ src/enums.h 2008-07-17 18:33:07 +0000
@@ -177,7 +177,8 @@
ANY_OLD_PARENT,
USERHASH_PARENT,
SOURCEHASH_PARENT,
- HIER_MAX
+ HIER_MAX,
+ PINNED
} hier_code;
/// \ingroup ServerProtocolICPAPI
=== modified file 'src/forward.cc'
--- src/forward.cc 2008-09-18 09:46:56 +0000
+++ src/forward.cc 2008-09-20 15:15:50 +0000
@@ -804,6 +804,35 @@
if (ftimeout < ctimeout)
ctimeout = ftimeout;
+
+ request->flags.pinned = 0;
+ if (fs->code == PINNED) {
+ int auth;
+ assert(request->pinned_connection);
+ fd = request->pinned_connection->getPinnedConnection(request, fs->_peer, auth);
+ if (fd >= 0) {
+#if 0
+ if (!fs->_peer)
+ fs->code = HIER_DIRECT;
+#endif
+ server_fd = fd;
+ n_tries++;
+ request->flags.pinned = 1;
+ if (auth)
+ request->flags.auth = 1;
+ comm_add_close_handler(fd, fwdServerClosedWrapper, this);
+ connectDone(fd, COMM_OK, 0);
+ return;
+ }
+ /* Failure. Fall back on next path */
+ cbdataReferenceDone(request->pinned_connection);
+ request->pinned_connection = NULL;
+ servers = fs->next;
+ fwdServerFree(fs);
+ connectStart();
+ return;
+ }
+
fd = fwdPconnPool->pop(host, port, domain, client_addr, checkRetriable());
if (fd >= 0) {
debugs(17, 3, "fwdConnectStart: reusing pconn FD " << fd);
=== modified file 'src/http.cc'
--- src/http.cc 2008-09-13 13:43:00 +0000
+++ src/http.cc 2008-09-14 13:22:13 +0000
@@ -377,7 +377,7 @@
}
}
- if (request->flags.auth) {
+ if (request->flags.auth || request->flags.auth_sent) {
/*
* Responses to requests with authorization may be cached
* only if a Cache-Control: public reply header is present.
@@ -709,6 +709,9 @@
httpChunkDecoder = new ChunkedCodingParser;
}
+ if(!peerSupportsConnectionPinning())
+ orig_request->flags.no_connection_auth = 1;
+
HttpReply *vrep = setVirginReply(newrep);
flags.headers_parsed = 1;
@@ -726,6 +729,67 @@
}
+/**
+ * returns true if the peer can support connection pinning
+*/
+bool HttpStateData::peerSupportsConnectionPinning()
+{
+ const HttpReply *rep = entry->mem_obj->getReply();
+ const HttpHeader *hdr = &rep->header;
+ bool rc;
+ String header;
+
+ if (!_peer)
+ return true;
+
+ /*If this peer does not support connection pinning (authenticated
+ connections) return false
+ */
+ if (!_peer->connection_auth)
+ return false;
+
+ /*The peer supports connection pinning and the http reply status
+ is not unauthorized, so the related connection can be pinned
+ */
+ if (rep->sline.status != HTTP_UNAUTHORIZED)
+ return true;
+
+ /*The server respond with HTTP_UNAUTHORIZED and the peer configured
+ with "connection-auth=on" we know that the peer supports pinned
+ connections
+ */
+ if (_peer->connection_auth == 1)
+ return true;
+
+ /*At this point peer has configured with "connection-auth=auto"
+ parameter so we need some extra checks to decide if we are going
+ to allow pinned connections or not
+ */
+
+ /*if the peer configured with originserver just allow connection
+ pinning (squid 2.6 behaviour)
+ */
+ if (_peer->options.originserver)
+ return true;
+
+ /*if the connections it is already pinned it is OK*/
+ if (request->flags.pinned)
+ return true;
+
+ /*Allow pinned connections only if the Proxy-support header exists in
+ reply and has in its list the "Session-Based-Authentication"
+ which means that the peer supports connection pinning.
+ */
+ if (!hdr->has(HDR_PROXY_SUPPORT))
+ return false;
+
+ header = hdr->getStrOrList(HDR_PROXY_SUPPORT);
+ /* XXX This ought to be done in a case-insensitive manner */
+ rc = (strstr(header.buf(), "Session-Based-Authentication") != NULL);
+
+ return rc;
+}
+
// Called when we parsed (and possibly adapted) the headers but
// had not starting storing (a.k.a., sending) the body yet.
void
@@ -1135,6 +1199,7 @@
{
AsyncCall::Pointer call;
IPAddress client_addr;
+ bool ispinned = false;
if (!flags.headers_parsed) {
flags.do_next_read = 1;
@@ -1200,7 +1265,16 @@
if (orig_request->flags.spoof_client_ip)
client_addr = orig_request->client_addr;
- if (_peer) {
+
+ if (request->flags.pinned) {
+ ispinned = true;
+ } else if (request->flags.connection_auth && request->flags.auth_sent) {
+ ispinned = true;
+ }
+
+ if (orig_request->pinned_connection && ispinned) {
+ orig_request->pinned_connection->pinConnection(fd, orig_request, _peer, request->flags.connection_auth);
+ } else if (_peer) {
if (_peer->options.originserver)
fwd->pconnPush(fd, _peer->name, orig_request->port, orig_request->GetHost(), client_addr);
else
@@ -1709,7 +1783,7 @@
*/
if (NULL == orig_request->range || !orig_request->flags.cachable
- || orig_request->range->offsetLimitExceeded())
+ || orig_request->range->offsetLimitExceeded() || orig_request->flags.connection_auth)
result = false;
debugs(11, 8, "decideIfWeDoRanges: range specs: " <<
@@ -1739,6 +1813,12 @@
HttpHeader hdr(hoRequest);
Packer p;
httpBuildRequestHeader(request, orig_request, entry, &hdr, flags);
+
+ if (request->flags.pinned && request->flags.connection_auth)
+ request->flags.auth_sent = 1;
+ else if (hdr.has(HDR_AUTHORIZATION))
+ request->flags.auth_sent = 1;
+
packerToMemInit(&p, mb);
hdr.packInto(&p);
hdr.clean();
@@ -1792,7 +1872,9 @@
/*
* Is keep-alive okay for all request methods?
*/
- if (!Config.onoff.server_pconns)
+ if (orig_request->flags.must_keepalive)
+ flags.keepalive = 1;
+ else if (!Config.onoff.server_pconns)
flags.keepalive = 0;
else if (_peer == NULL)
flags.keepalive = 1;
=== modified file 'src/http.h'
--- src/http.h 2008-09-13 13:43:00 +0000
+++ src/http.h 2008-09-14 13:22:13 +0000
@@ -118,6 +118,7 @@
MemBuf * mb,
http_state_flags flags);
static bool decideIfWeDoRanges (HttpRequest * orig_request);
+ bool peerSupportsConnectionPinning();
ChunkedCodingParser *httpChunkDecoder;
private:
=== modified file 'src/neighbors.cc'
--- src/neighbors.cc 2008-07-18 11:24:16 +0000
+++ src/neighbors.cc 2008-09-20 15:39:41 +0000
@@ -51,7 +51,7 @@
/* count mcast group peers every 15 minutes */
#define MCAST_COUNT_RATE 900
-static int peerAllowedToUse(const peer *, HttpRequest *);
+int peerAllowedToUse(const peer *, HttpRequest *);
static int peerWouldBePinged(const peer *, HttpRequest *);
static void neighborRemove(peer *);
static void neighborAlive(peer *, const MemObject *, const icp_common_t *);
@@ -136,7 +136,7 @@
* this function figures out if it is appropriate to fetch REQUEST
* from PEER.
*/
-static int
+int
peerAllowedToUse(const peer * p, HttpRequest * request)
{
@@ -1635,6 +1635,13 @@
if (p->domain)
storeAppendPrintf(sentry, " forceddomain=%s", p->domain);
+ if(p->connection_auth == 0)
+ storeAppendPrintf(sentry, " connection-auth=off");
+ else if(p->connection_auth == 1)
+ storeAppendPrintf(sentry, " connection-auth=on");
+ else if(p->connection_auth == 2)
+ storeAppendPrintf(sentry, " connection-auth=auto");
+
storeAppendPrintf(sentry, "\n");
}
=== modified file 'src/peer_select.cc'
--- src/peer_select.cc 2008-07-11 20:43:43 +0000
+++ src/peer_select.cc 2008-09-07 14:20:04 +0000
@@ -100,6 +100,7 @@
static void peerGetSomeParent(ps_state *);
static void peerGetAllParents(ps_state *);
static void peerAddFwdServer(FwdServer **, peer *, hier_code);
+static void peerGetPinned(ps_state * ps);
CBDATA_CLASS_INIT(ps_state);
@@ -322,6 +323,8 @@
debugs(44, 3, "peerSelectFoo: direct = " << DirectStr[ps->direct]);
}
+ if (!entry || entry->ping_status == PING_NONE)
+ peerGetPinned(ps);
if (entry == NULL) {
(void) 0;
} else if (entry->ping_status == PING_NONE) {
@@ -363,6 +366,32 @@
}
/*
+ * peerGetPinned
+ *
+ * Selects a pinned connection
+ */
+int peerAllowedToUse(const peer * p, HttpRequest * request);
+static void
+peerGetPinned(ps_state * ps)
+{
+ HttpRequest *request = ps->request;
+ peer *peer;
+ if (!request->pinned_connection)
+ return;
+ if (request->pinned_connection->getPinnedInfo(request, peer) != -1) {
+ if (peer && peerAllowedToUse(peer, request)) {
+ peerAddFwdServer(&ps->servers, peer, PINNED);
+ if (ps->entry)
+ ps->entry->ping_status = PING_DONE; /* Skip ICP */
+ } else if (!peer && ps->direct != DIRECT_NO) {
+ peerAddFwdServer(&ps->servers, NULL, PINNED);
+ if (ps->entry)
+ ps->entry->ping_status = PING_DONE; /* Skip ICP */
+ }
+ }
+}
+
+/*
* peerGetSomeNeighbor
*
* Selects a neighbor (parent or sibling) based on one of the
=== modified file 'src/structs.h'
--- src/structs.h 2008-08-09 06:24:33 +0000
+++ src/structs.h 2008-09-02 08:32:52 +0000
@@ -1011,6 +1011,7 @@
#endif
int front_end_https;
+ int connection_auth;
};
struct _net_db_name
@@ -1095,6 +1096,11 @@
unsigned int internal:1;
unsigned int internalclient:1;
unsigned int must_keepalive:1;
+ unsigned int connection_auth:1; /** Request wants connection oriented auth */
+ unsigned int no_connection_auth:1; /** Connection oriented auth can not be supported */
+ unsigned int connection_proxy_auth:1; /** Request wants connection oriented auth */
+ unsigned int pinned:1; /* Request seont on a pinned connection */
+ unsigned int auth_sent:1; /* Authentication forwarded */
// When adding new flags, please update cloneAdaptationImmune() as needed.
# Begin bundle
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
l3MyB06tBvvCb6Q57QPRxnYR0d0MESTT0+Tx+L7w4jCoKP8n48BpoVL8JrZkMOGaf6ajW4jm226k
LMExr9lOSfmn6b65PD0UdSBtPU0l+bObR0rgmEj0wXXX3/D8HxUDu9/QCtPOZSkiZ+l9ybVo/Wfr
UWUbKs2+uM5Z5LfxmbJhoozWZP2pNn8jVmTbvmwscNGzZdw/Us82a9LJ3ZJNlGrhdZk4Wf092q7J
q1YdNHCpBkcufg+3QySYODgg0WsxQyKSd0Ekmf0Es97vTDZo6cJNl1lXCaaijzEEdtEnho8m0Giy
aTVOyirV7MK57pTyWXcuGzhs8t26jNm1ZLpGvOqzJyzZmyzNuku8+bsLuDV9mrxCMkmTKEUezDp2
k5WSeSxd08Mnlh2Zr1S6XWbqrskkmzdagHk5tSQwff0xSagoJTW8gPLCB3P7qywibduhIXEIQKhi
owLty56KGozExcPNScgUFxKbTaXhpMZQ0hr5vdUAieFw09D9ng7VbQtE9QBtAf7+BunQ8Rwa0cHm
PkXIXZ5YedHzpsLUAA1QVE5vYlgC8XTxSmf4N4qFwoPOcReYjHcewcTmvZmgVkDh1GpLSE8WqHHe
QO8tKCY/IkuyatogzZp3bKNFlGyyTJNy+tqybtHDZ11k2KtWzlJmk/TGjwydOV3hhmizw7ZvDMsk
4RtZKZJyycqqMmrVo3auFSGjx4qq0dumFlHDNu8NkzDN0q333VbMnh01KunhZ011ybMJs2jRNyo2
bJLsnzQptylHDNVow7bpOmzwVdsPT2zVhElCJQVlyDJH+aGRwxLjuHlfNOBwJi48g7KAjsJVlhoa
DjaVmJtIjxwbzUiDy0+pg/EdWGb/t/12G8nPMAEanIxIBgXFx0GCg7dqvtYaVXaPtRw+z7830KtH
0OFVnCaar6oR+CImZPLp0qdO27w2axGSbNm1u3bPw+0VQcGUoZzlV3F6Fi6spOoyBkMBUtMZnCoY
cRNpIMYmnTE6h66AJ3IKW5t1+bBAnnjP+0Cec85AZJ7ABLBKCWCUEoNiUGxKDYlIywSwiQpBKAws
iUGxKDYlBsSwiQsBKDRLBLEsBJSJSMsEoNEsEoJYJQSwEliWRCxKDRLHuJ06uuE/EQyFnZS0B7Kg
WGZxHoo0QxoRGBBIMGJBKHIAej2CXnOkDqOwVfSYKLP/3BTb3JGXAhJ+31F9VM4xozO22kwggJAy
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