From: Amos Jeffries Date: Sat, 4 Oct 2008 12:46:16 +0000 (+1300) Subject: Author: Christos Tsantilas X-Git-Tag: SQUID_3_1_0_1~51 X-Git-Url: http://git.ipfire.org/gitweb/?a=commitdiff_plain;h=d67acb4e19a325dae3694c1944ba2239df0c49ba;p=thirdparty%2Fsquid.git Author: Christos Tsantilas Port Connection Pinning from 2.6 --- diff --git a/src/HttpHeader.cc b/src/HttpHeader.cc index 9d457c2db5..c77ebdcbb8 100644 --- a/src/HttpHeader.cc +++ b/src/HttpHeader.cc @@ -112,6 +112,7 @@ static const HttpHeaderFieldAttrs HeadersAttrs[] = {"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 @@ static http_hdr_type ListHeadersArr[] = HDR_IF_MATCH, HDR_IF_NONE_MATCH, HDR_LINK, HDR_PRAGMA, HDR_PROXY_CONNECTION, + HDR_PROXY_SUPPORT, HDR_TRANSFER_ENCODING, HDR_UPGRADE, HDR_VARY, diff --git a/src/HttpHeader.h b/src/HttpHeader.h index 74225a9949..4eba065dba 100644 --- a/src/HttpHeader.h +++ b/src/HttpHeader.h @@ -83,6 +83,7 @@ typedef enum { 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 */ diff --git a/src/HttpMsg.h b/src/HttpMsg.h index 7ecb1ed0f9..184850a8f2 100644 --- a/src/HttpMsg.h +++ b/src/HttpMsg.h @@ -92,6 +92,8 @@ public: virtual bool expectingBody(const HttpRequestMethod&, int64_t&) const = 0; void firstLineBuf(MemBuf&); + + virtual bool inheritProperties(const HttpMsg *aMsg) = 0; protected: virtual bool sanityCheckStartLine(MemBuf *buf, http_status *error) = 0; diff --git a/src/HttpReply.cc b/src/HttpReply.cc index bfbd0b06fe..6326350e1d 100644 --- a/src/HttpReply.cc +++ b/src/HttpReply.cc @@ -566,5 +566,16 @@ HttpReply::clone() const rep->protocol = protocol; rep->sline = sline; + rep->keep_alive = keep_alive; return rep; } + + +bool HttpReply::inheritProperties(const HttpMsg *aMsg) +{ + const HttpReply *aRep = dynamic_cast(aMsg); + if(!aRep) + return false; + keep_alive = aRep->keep_alive; + return true; +} diff --git a/src/HttpReply.h b/src/HttpReply.h index 2fe554cd29..baf595fc2a 100644 --- a/src/HttpReply.h +++ b/src/HttpReply.h @@ -99,6 +99,8 @@ public: virtual bool expectingBody(const HttpRequestMethod&, int64_t&) const; + virtual bool inheritProperties(const HttpMsg *aMsg); + void updateOnNotModified(HttpReply const *other); /** set commonly used info with one call */ diff --git a/src/HttpRequest.cc b/src/HttpRequest.cc index 3f17df12ec..73e57a1204 100644 --- a/src/HttpRequest.cc +++ b/src/HttpRequest.cc @@ -74,6 +74,7 @@ HttpRequest::init() 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,9 @@ HttpRequest::clean() range = NULL; } + if(pinned_connection) + cbdataReferenceDone(pinned_connection); + tag.clean(); extacl_user.clean(); @@ -510,3 +514,27 @@ HttpRequest::cacheable() const return true; } + +bool HttpRequest::inheritProperties(const HttpMsg *aMsg) +{ + const HttpRequest* aReq = dynamic_cast(aMsg); + if(!aReq) + return false; + + client_addr = aReq->client_addr; + my_addr = aReq->my_addr; + + // This may be too conservative for the 204 No Content case + // may eventually need cloneNullAdaptationImmune() for that. + flags = aReq->flags.cloneAdaptationImmune(); + + if (aReq->auth_user_request) { + auth_user_request = aReq->auth_user_request; + AUTHUSERREQUESTLOCK(auth_user_request, "inheritProperties"); + } + + if(aReq->pinned_connection) { + pinned_connection = cbdataReference(aReq->pinned_connection); + } + return true; +} diff --git a/src/HttpRequest.h b/src/HttpRequest.h index 2dcec900a3..4d71f0e123 100644 --- a/src/HttpRequest.h +++ b/src/HttpRequest.h @@ -100,6 +100,12 @@ public: private: char host[SQUIDHOSTNAMELEN]; + + /*** + * The client side connection data of pinned connections for the client side + * request related objects + */ + ConnStateData *pinned_connection; public: IPAddress host_addr; @@ -176,6 +182,18 @@ public: static HttpRequest * CreateFromUrlAndMethod(char * url, const HttpRequestMethod& method); static HttpRequest * CreateFromUrl(char * url); + + void setPinnedConnection(ConnStateData *conn) { + pinned_connection = cbdataReference(conn); + } + + ConnStateData *pinnedConnection() { + return pinned_connection; + } + + void releasePinnedConnection() { + cbdataReferenceDone(pinned_connection); + } private: const char *packableURI(bool full_uri) const; @@ -186,7 +204,8 @@ protected: virtual bool sanityCheckStartLine(MemBuf *buf, http_status *error); virtual void hdrCacheInit(); - + + virtual bool inheritProperties(const HttpMsg *aMsg); }; MEMPROXY_CLASS_INLINE(HttpRequest) /**DOCS_NOSEMI*/ diff --git a/src/ICAP/ICAPModXact.cc b/src/ICAP/ICAPModXact.cc index ff0412efdc..4bfdb9c41d 100644 --- a/src/ICAP/ICAPModXact.cc +++ b/src/ICAP/ICAPModXact.cc @@ -782,14 +782,16 @@ void ICAPModXact::prepEchoing() // allocate the adapted message and copy metainfo Must(!adapted.header); HttpMsg *newHead = NULL; - if (const HttpRequest *oldR = dynamic_cast(oldHead)) { + if (dynamic_cast(oldHead)) { HttpRequest *newR = new HttpRequest; - inheritVirginProperties(*newR, *oldR); newHead = newR; - } else - if (dynamic_cast(oldHead)) - newHead = new HttpReply; + } + else if (dynamic_cast(oldHead)) { + HttpReply *newRep = new HttpReply; + newHead = newRep; + } Must(newHead); + newHead->inheritProperties(oldHead); adapted.setHeader(newHead); @@ -845,15 +847,18 @@ void ICAPModXact::parseHttpHead() if (!parseHead(adapted.header)) return; // need more header data - if (HttpRequest *newHead = dynamic_cast(adapted.header)) { + if (dynamic_cast(adapted.header)) { const HttpRequest *oldR = dynamic_cast(virgin.header); Must(oldR); // TODO: the adapted request did not really originate from the // client; give proxy admin an option to prevent copying of // sensitive client information here. See the following thread: // http://www.squid-cache.org/mail-archive/squid-dev/200703/0040.html - inheritVirginProperties(*newHead, *oldR); } + + // Maybe adapted.header==NULL if HttpReply and have Http 0.9 .... + if(adapted.header) + adapted.header->inheritProperties(virgin.header); } decideOnParsingBody(); @@ -881,22 +886,6 @@ bool ICAPModXact::parseHead(HttpMsg *head) return true; } -// TODO: Move this method to HttpRequest? -void ICAPModXact::inheritVirginProperties(HttpRequest &newR, const HttpRequest &oldR) { - - newR.client_addr = oldR.client_addr; - newR.my_addr = oldR.my_addr; - - // This may be too conservative for the 204 No Content case - // may eventually need cloneNullAdaptationImmune() for that. - newR.flags = oldR.flags.cloneAdaptationImmune(); - - if (oldR.auth_user_request) { - newR.auth_user_request = oldR.auth_user_request; - AUTHUSERREQUESTLOCK(newR.auth_user_request, "newR in ICAPModXact"); - } -} - void ICAPModXact::decideOnParsingBody() { if (gotEncapsulated("res-body") || gotEncapsulated("req-body")) { debugs(93, 5, HERE << "expecting a body"); @@ -1152,7 +1141,6 @@ void ICAPModXact::encapsulateHead(MemBuf &icapBuf, const char *section, MemBuf & HttpRequest* new_request = new HttpRequest; urlParse(old_request->method, old_request->canonical,new_request); new_request->http_ver = old_request->http_ver; - inheritVirginProperties(*new_request, *old_request); headClone = new_request; } else if (const HttpReply *old_reply = dynamic_cast(head)) { @@ -1162,6 +1150,7 @@ void ICAPModXact::encapsulateHead(MemBuf &icapBuf, const char *section, MemBuf & } Must(headClone); + headClone->inheritProperties(head); HttpHeaderPos pos = HttpHeaderInitPos; HttpHeaderEntry* p_head_entry = NULL; diff --git a/src/ICAP/ICAPModXact.h b/src/ICAP/ICAPModXact.h index 1c07fec3be..f546bfa550 100644 --- a/src/ICAP/ICAPModXact.h +++ b/src/ICAP/ICAPModXact.h @@ -208,7 +208,6 @@ private: void parseIcapHead(); void parseHttpHead(); bool parseHead(HttpMsg *head); - void inheritVirginProperties(HttpRequest &newR, const HttpRequest &oldR); void decideOnParsingBody(); void parseBody(); diff --git a/src/ProtoPort.h b/src/ProtoPort.h index a9ece3f2c2..72f64bbce3 100644 --- a/src/ProtoPort.h +++ b/src/ProtoPort.h @@ -26,6 +26,7 @@ struct http_port_list unsigned int sslBump:1; /**< intercepts CONNECT requests */ int vport; /* virtual port support, -1 for dynamic, >0 static*/ + bool connection_auth_disabled; /* Don't support connection oriented auth */ int disable_pmtu_discovery; struct { diff --git a/src/cache_cf.cc b/src/cache_cf.cc index 1cd44b7094..ad85e549ea 100644 --- a/src/cache_cf.cc +++ b/src/cache_cf.cc @@ -1690,6 +1690,7 @@ parse_peer(peer ** head) self_destruct(); p->icp.port = GetUdpService(); + p->connection_auth = 2; /* auto */ while ((token = strtok(NULL, w_space))) { if (!strcasecmp(token, "proxy-only")) { @@ -1843,6 +1844,14 @@ parse_peer(peer ** head) 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(); @@ -2867,6 +2876,7 @@ parse_http_port_specification(http_port_list * s, char *token) s->disable_pmtu_discovery = DISABLE_PMTU_OFF; s->name = xstrdup(token); + s->connection_auth_disabled = false; #if USE_IPV6 if (*token == '[') { @@ -2950,6 +2960,14 @@ parse_http_port_option(http_port_list * s, char *token) s->accel = 1; } else if (strcmp(token, "accel") == 0) { s->accel = 1; + } else if (strcmp(token, "no-connection-auth") == 0) { + s->connection_auth_disabled = true; + } else if (strcmp(token, "connection-auth=off") == 0) { + s->connection_auth_disabled = true; + } else if (strcmp(token, "connection-auth") == 0) { + s->connection_auth_disabled = false; + } else if (strcmp(token, "connection-auth=on") == 0) { + s->connection_auth_disabled = false; } else if (strncmp(token, "disable-pmtu-discovery=", 23) == 0) { if (!strcasecmp(token + 23, "off")) s->disable_pmtu_discovery = DISABLE_PMTU_OFF; @@ -3131,6 +3149,11 @@ dump_generic_http_port(StoreEntry * e, const char *n, const http_port_list * s) if (s->vport) storeAppendPrintf(e, " vport"); + if (s->connection_auth_disabled) + storeAppendPrintf(e, " connection-auth=off"); + else + storeAppendPrintf(e, " connection-auth=on"); + if (s->disable_pmtu_discovery != DISABLE_PMTU_OFF) { const char *pmtu; diff --git a/src/cf.data.pre b/src/cf.data.pre index 9c485b5161..452e0c5910 100644 --- a/src/cf.data.pre +++ b/src/cf.data.pre @@ -1033,6 +1033,11 @@ DOC_START protocol= Protocol to reconstruct accelerated requests with. Defaults to http. + connection-auth[=on|off] + use connection-auth=off to tell Squid to prevent + forwarding 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). @@ -1602,6 +1607,7 @@ DOC_START 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. @@ -1824,6 +1830,12 @@ DOC_START 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 diff --git a/src/client_side.cc b/src/client_side.cc index 202a1b502e..789a1f2f82 100644 --- a/src/client_side.cc +++ b/src/client_side.cc @@ -621,6 +621,9 @@ ConnStateData::swanSong() auth_user_request->onConnectionClose(this); } + if (pinning.fd >= 0) + comm_close(pinning.fd); + BodyProducer::swanSong(); flags.swanSang = true; } @@ -1368,6 +1371,12 @@ ClientSocketContext::keepaliveNextRequest() debugs(33, 3, "ClientSocketContext::keepaliveNextRequest: FD " << conn->fd); connIsFinished(); + if (conn->pinning.pinned && conn->pinning.fd == -1) { + debugs(33, 2, "clientKeepaliveNextRequest: FD " << conn->fd << " Connection was pinned but server side gone. Terminating client connection"); + 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 @@ -3353,6 +3362,9 @@ CBDATA_CLASS_INIT(ConnStateData); ConnStateData::ConnStateData() :AsyncJob("ConnStateData"), transparent_ (false), reading_ (false), closing_ (false) { + pinning.fd = -1; + pinning.pinned = false; + pinning.auth = false; } bool @@ -3432,3 +3444,96 @@ ConnStateData::In::~In() if (allocatedSize) memFreeBuf(allocatedSize, buf); } + +/* This is a comm call normally scheduled by comm_close() */ +void +ConnStateData::clientPinnedConnectionClosed(const CommCloseCbParams &io) +{ + pinning.fd = -1; + if (pinning.peer) { + cbdataReferenceDone(pinning.peer); + } + safe_free(pinning.host); + /* NOTE: pinning.pinned should be kept. This combined with fd == -1 at the end of a request indicates that the host + * connection has gone away */ +} + +void ConnStateData::pinConnection(int pinning_fd, HttpRequest *request, struct peer *peer, bool 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 = true; + 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); + + typedef CommCbMemFunT Dialer; + pinning.closeHandler = asyncCall(33, 5, "ConnStateData::clientPinnedConnectionClosed", + Dialer(this, &ConnStateData::clientPinnedConnectionClosed)); + comm_add_close_handler(pinning_fd, pinning.closeHandler); + +} + +int ConnStateData::validatePinnedConnection(HttpRequest *request, const struct peer *peer) +{ + bool valid = true; + if (pinning.fd < 0) + return -1; + + if (pinning.auth && request && strcasecmp(pinning.host, request->GetHost()) != 0) { + valid = false; + } + if (request && pinning.port != request->port){ + valid = false; + } + if (pinning.peer && !cbdataReferenceValid(pinning.peer)){ + valid = false; + } + if (peer != pinning.peer){ + valid = false; + } + + if(!valid) { + int pinning_fd=pinning.fd; + /* The pinning info is not safe, remove any pinning info*/ + unpinConnection(); + + /* also close the server side socket, we should not use it for invalid/unauthenticated + requests... + */ + comm_close(pinning_fd); + return -1; + } + + return pinning.fd; +} + +void ConnStateData::unpinConnection() +{ + if(pinning.peer) + cbdataReferenceDone(pinning.peer); + + if(pinning.closeHandler != NULL) { + comm_remove_close_handler(pinning.fd, pinning.closeHandler); + pinning.closeHandler = NULL; + } + pinning.fd = -1; + safe_free(pinning.host); +} diff --git a/src/client_side.h b/src/client_side.h index def37ed7cf..2010f131bb 100644 --- a/src/client_side.h +++ b/src/client_side.h @@ -189,6 +189,16 @@ public: 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 */ + bool pinned; /* this connection was pinned */ + bool auth; /* pinned for www authentication */ + struct peer *peer; /* peer the connection goes via */ + AsyncCall::Pointer closeHandler; /*The close handler for pinned server side connection*/ + } pinning; + http_port_list *port; bool transparent() const; @@ -206,6 +216,31 @@ public: void handleReadData(char *buf, size_t size); void handleRequestBodyData(); + /** + * Correlate the current ConnStateData object with the pinning_fd socket descriptor. + */ + void pinConnection(int fd, HttpRequest *request, struct peer *peer, bool auth); + /** + * Decorrelate the ConnStateData object from its pinned peer + */ + void unpinConnection(); + /** + * Checks if there is pinning info if it is valid. It can close the server side connection + * if pinned info is not valid. + \param request if it is not NULL also checks if the pinning info refers to the request client side HttpRequest + \param peer if it is not NULL also check if the peer is the pinning peer + \return The fd of the server side connection or -1 if fails. + */ + int validatePinnedConnection(HttpRequest *request, const struct peer *peer=NULL); + /** + * returts the pinned peer if exists, NULL otherwise + */ + struct peer *pinnedPeer() const {return pinning.peer;} + bool pinnedAuth() const {return pinning.auth;} + + // pining related comm callbacks + void clientPinnedConnectionClosed(const CommCloseCbParams &io); + // comm callbacks void clientReadRequest(const CommIoCbParams &io); void connStateClosed(const CommCloseCbParams &io); diff --git a/src/client_side_reply.cc b/src/client_side_reply.cc index e3b65e69e4..7bb1f456f6 100644 --- a/src/client_side_reply.cc +++ b/src/client_side_reply.cc @@ -1277,24 +1277,45 @@ clientReplyContext::buildReplyHeader() /* 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.connection_auth_disabled) { + hdr->delAt(pos, connection_auth_blocked); + continue; + } + request->flags.must_keepalive = 1; + if (!request->flags.accelerated && !request->flags.intercepted) { + httpHeaderPutStrf(hdr, HDR_PROXY_SUPPORT, "Session-Based-Authentication"); + /* + We send "[Proxy-]Connection: Proxy-Support" header to mark + Proxy-Support as a hop-by-hop header for intermediaries that do not + understand the semantics of this header. The RFC should have included + this recommendation. + */ + httpHeaderPutStrf(hdr, HDR_CONNECTION, "Proxy-support"); + } + break; + } } } - if (headers_deleted) + + if (connection_auth_blocked) hdr->refreshMask(); } @@ -1352,6 +1373,12 @@ clientReplyContext::buildReplyHeader() debugs(88, 3, "clientBuildReplyHeader: Shutting down, don't keep-alive."); request->flags.proxy_keepalive = 0; } + + if (request->flags.connection_auth && !reply->keep_alive) { + debugs(33, 2, "clientBuildReplyHeader: Connection oriented auth but server side non-persistent"); + request->flags.proxy_keepalive = 0; + } + /* Append VIA */ if (Config.onoff.via) { diff --git a/src/client_side_request.cc b/src/client_side_request.cc index 5b6ca63393..49a4219ae8 100644 --- a/src/client_side_request.cc +++ b/src/client_side_request.cc @@ -828,6 +828,56 @@ clientInterpretRequestHeaders(ClientHttpRequest * http) if (req_hdr->has(HDR_AUTHORIZATION)) request->flags.auth = 1; + ConnStateData *http_conn = http->getConn(); + assert(http_conn); + request->flags.connection_auth_disabled = http_conn->port->connection_auth_disabled; + if (!request->flags.connection_auth_disabled) { + 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->setPinnedConnection(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->pinnedConnection()) { + request->setPinnedConnection(http->getConn()); + } + } + } + + if (request->login[0] != '\0') request->flags.auth = 1; diff --git a/src/enums.h b/src/enums.h index 5ab13f8c7c..0b8a164086 100644 --- a/src/enums.h +++ b/src/enums.h @@ -177,7 +177,8 @@ typedef enum { ANY_OLD_PARENT, USERHASH_PARENT, SOURCEHASH_PARENT, - HIER_MAX + HIER_MAX, + PINNED } hier_code; /// \ingroup ServerProtocolICPAPI diff --git a/src/forward.cc b/src/forward.cc index 91da4cbbaf..18e3fee05a 100644 --- a/src/forward.cc +++ b/src/forward.cc @@ -804,6 +804,35 @@ FwdState::connectStart() if (ftimeout < ctimeout) ctimeout = ftimeout; + + request->flags.pinned = 0; + if (fs->code == PINNED) { + ConnStateData *pinned_connection = request->pinnedConnection(); + assert(pinned_connection); + fd = pinned_connection->validatePinnedConnection(request, fs->_peer); + if (fd >= 0) { + pinned_connection->unpinConnection(); +#if 0 + if (!fs->_peer) + fs->code = HIER_DIRECT; +#endif + server_fd = fd; + n_tries++; + request->flags.pinned = 1; + if (pinned_connection->pinnedAuth()) + request->flags.auth = 1; + comm_add_close_handler(fd, fwdServerClosedWrapper, this); + connectDone(fd, COMM_OK, 0); + return; + } + /* Failure. Fall back on next path */ + request->releasePinnedConnection(); + 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); diff --git a/src/http.cc b/src/http.cc index c940a4927d..f20c942c9e 100644 --- a/src/http.cc +++ b/src/http.cc @@ -383,7 +383,7 @@ HttpStateData::cacheableReply() } } - 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. @@ -715,6 +715,9 @@ HttpStateData::processReplyHeader() httpChunkDecoder = new ChunkedCodingParser; } + if(!peerSupportsConnectionPinning()) + orig_request->flags.connection_auth_disabled = 1; + HttpReply *vrep = setVirginReply(newrep); flags.headers_parsed = 1; @@ -732,6 +735,67 @@ HttpStateData::processReplyHeader() } +/** + * returns true if the peer can support connection pinning +*/ +bool HttpStateData::peerSupportsConnectionPinning() const +{ + 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 @@ -1141,6 +1205,7 @@ HttpStateData::processReplyBody() { AsyncCall::Pointer call; IPAddress client_addr; + bool ispinned = false; if (!flags.headers_parsed) { flags.do_next_read = 1; @@ -1206,7 +1271,17 @@ HttpStateData::processReplyBody() 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->pinnedConnection() && ispinned) { + orig_request->pinnedConnection()->pinConnection(fd, orig_request, _peer, + (request->flags.connection_auth != 0)); + } else if (_peer) { if (_peer->options.originserver) fwd->pconnPush(fd, _peer->name, orig_request->port, orig_request->GetHost(), client_addr); else @@ -1715,7 +1790,7 @@ HttpStateData::decideIfWeDoRanges (HttpRequest * orig_request) */ 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: " << @@ -1745,6 +1820,12 @@ HttpStateData::buildRequestPrefix(HttpRequest * request, 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(); @@ -1798,7 +1879,9 @@ HttpStateData::sendRequest() /* * 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; diff --git a/src/http.h b/src/http.h index 59bc5d71d1..7c1ee2daa9 100644 --- a/src/http.h +++ b/src/http.h @@ -118,6 +118,7 @@ private: MemBuf * mb, http_state_flags flags); static bool decideIfWeDoRanges (HttpRequest * orig_request); + bool peerSupportsConnectionPinning() const; ChunkedCodingParser *httpChunkDecoder; private: diff --git a/src/neighbors.cc b/src/neighbors.cc index c01498c832..cb8bd183ad 100644 --- a/src/neighbors.cc +++ b/src/neighbors.cc @@ -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 @@ neighborType(const peer * p, const HttpRequest * request) * this function figures out if it is appropriate to fetch REQUEST * from PEER. */ -static int +int peerAllowedToUse(const peer * p, HttpRequest * request) { @@ -1653,6 +1653,13 @@ dump_peer_options(StoreEntry * sentry, peer * p) 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"); } diff --git a/src/peer_select.cc b/src/peer_select.cc index 952f2341c9..b0e5008d9e 100644 --- a/src/peer_select.cc +++ b/src/peer_select.cc @@ -100,6 +100,7 @@ static void peerGetSomeDirect(ps_state *); static void peerGetSomeParent(ps_state *); static void peerGetAllParents(ps_state *); static void peerAddFwdServer(FwdServer **, peer *, hier_code); +static void peerSelectPinned(ps_state * ps); CBDATA_CLASS_INIT(ps_state); @@ -322,6 +323,8 @@ peerSelectFoo(ps_state * ps) debugs(44, 3, "peerSelectFoo: direct = " << DirectStr[ps->direct]); } + if (!entry || entry->ping_status == PING_NONE) + peerSelectPinned(ps); if (entry == NULL) { (void) 0; } else if (entry->ping_status == PING_NONE) { @@ -362,6 +365,33 @@ peerSelectFoo(ps_state * ps) peerSelectCallback(ps); } +/* + * peerSelectPinned + * + * Selects a pinned connection + */ +int peerAllowedToUse(const peer * p, HttpRequest * request); +static void +peerSelectPinned(ps_state * ps) +{ + HttpRequest *request = ps->request; + peer *peer; + if (!request->pinnedConnection()) + return; + if (request->pinnedConnection()->validatePinnedConnection(request) != -1) { + peer = request->pinnedConnection()->pinnedPeer(); + 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 * diff --git a/src/structs.h b/src/structs.h index d47164f6d9..6057b4ba42 100644 --- a/src/structs.h +++ b/src/structs.h @@ -1016,6 +1016,7 @@ struct peer #endif int front_end_https; + int connection_auth; }; struct _net_db_name @@ -1100,6 +1101,11 @@ struct request_flags 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 connection_auth_disabled:1; /** Connection oriented auth can not be supported */ + unsigned int connection_proxy_auth:1; /** Request wants connection oriented auth */ + unsigned int pinned:1; /* Request sent on a pinned connection */ + unsigned int auth_sent:1; /* Authentication forwarded */ // When adding new flags, please update cloneAdaptationImmune() as needed. diff --git a/src/tests/stub_HttpReply.cc b/src/tests/stub_HttpReply.cc index 1e18328428..8c3b955226 100644 --- a/src/tests/stub_HttpReply.cc +++ b/src/tests/stub_HttpReply.cc @@ -115,3 +115,10 @@ HttpReply::clone() const fatal("Not implemented"); return NULL; } + +bool +HttpReply::inheritProperties(const HttpMsg *aMsg) +{ + fatal("Not implemented"); + return false; +} diff --git a/src/tests/stub_HttpRequest.cc b/src/tests/stub_HttpRequest.cc index a3f65ce7a8..f4bc033c9d 100644 --- a/src/tests/stub_HttpRequest.cc +++ b/src/tests/stub_HttpRequest.cc @@ -101,6 +101,13 @@ HttpRequest::clone() const return NULL; } +bool +HttpRequest::inheritProperties(const HttpMsg *aMsg) +{ + fatal("Not implemented"); + return false; +} + /* * DO NOT MODIFY: * arch-tag: dd894aa8-63cc-4543-92d9-1079a18bee11