=== modified file 'src/HttpRequest.cc' --- src/HttpRequest.cc 2014-04-27 07:59:17 +0000 +++ src/HttpRequest.cc 2014-05-11 07:29:34 +0000 @@ -262,40 +262,44 @@ #endif // This may be too conservative for the 204 No Content case // may eventually need cloneNullAdaptationImmune() for that. flags = aReq->flags.cloneAdaptationImmune(); errType = aReq->errType; errDetail = aReq->errDetail; #if USE_AUTH auth_user_request = aReq->auth_user_request; extacl_user = aReq->extacl_user; extacl_passwd = aReq->extacl_passwd; #endif myportname = aReq->myportname; // main property is which connection the request was received on (if any) clientConnectionManager = aReq->clientConnectionManager; notes = aReq->notes; + + // ensure the security context for this request is always copied + storeSecurityKey = aReq->storeSecurityKey; + return true; } /** * Checks the first line of an HTTP request is valid * currently just checks the request method is present. * * NP: Other errors are left for detection later in the parse. */ bool HttpRequest::sanityCheckStartLine(MemBuf *buf, const size_t hdr_len, Http::StatusCode *error) { // content is long enough to possibly hold a reply // 2 being magic size of a 1-byte request method plus space delimiter if ( buf->contentSize() < 2 ) { // this is ony a real error if the headers apparently complete. if (hdr_len > 0) { debugs(58, 3, HERE << "Too large request header (" << hdr_len << " bytes)"); *error = Http::scInvalidHeader; } === modified file 'src/HttpRequest.h' --- src/HttpRequest.h 2014-04-27 07:59:17 +0000 +++ src/HttpRequest.h 2014-05-10 14:01:20 +0000 @@ -209,40 +209,43 @@ NotePairs::Pointer notes; ///< annotations added by the note directive and helpers String tag; /* Internal tag for this request */ String extacl_user; /* User name returned by extacl lookup */ String extacl_passwd; /* Password returned by extacl lookup */ String extacl_log; /* String to be used for access.log purposes */ String extacl_message; /* String to be used for error page purposes */ #if FOLLOW_X_FORWARDED_FOR String x_forwarded_for_iterator; /* XXX a list of IP addresses */ #endif /* FOLLOW_X_FORWARDED_FOR */ /// A strong etag of the cached entry. Used for refreshing that entry. String etag; + /// An additional context to add to the store-ID key for security. + String storeSecurityKey; + public: bool multipartRangeRequest() const; bool parseFirstLine(const char *start, const char *end); int parseHeader(const char *parse_start, int len); virtual bool expectingBody(const HttpRequestMethod& unused, int64_t&) const; bool bodyNibbled() const; // the request has a [partially] consumed body int prefixLen(); void swapOut(StoreEntry * e); void pack(Packer * p); static void httpRequestPack(void *obj, Packer *p); static HttpRequest * CreateFromUrlAndMethod(char * url, const HttpRequestMethod& method); === modified file 'src/client_side_request.cc' --- src/client_side_request.cc 2014-04-27 07:59:17 +0000 +++ src/client_side_request.cc 2014-05-11 08:16:18 +0000 @@ -547,44 +547,50 @@ http->request->flags.hostVerified = true; http->doCallouts(); return; } debugs(85, 3, HERE << "validate IP " << clientConn->local << " non-match from Host: IP " << ia->in_addrs[i]); } } debugs(85, 3, HERE << "FAIL: validate IP " << clientConn->local << " possible from Host:"); hostHeaderVerifyFailed("local IP", "any domain IP"); } void ClientRequestContext::hostHeaderVerifyFailed(const char *A, const char *B) { // IP address validation for Host: failed. Admin wants to ignore them. // NP: we do not yet handle CONNECT tunnels well, so ignore for them if (!Config.onoff.hostStrictVerify && http->request->method != Http::METHOD_CONNECT) { debugs(85, 3, "SECURITY ALERT: Host header forgery detected on " << http->getConn()->clientConnection << " (" << A << " does not match " << B << ") on URL: " << urlCanonical(http->request)); +/* XXX // NP: it is tempting to use 'flags.noCache' but that is all about READing cache data. // The problems here are about WRITE for new cache content, which means flags.cachable http->request->flags.cachable = false; // MUST NOT cache (for now) - // XXX: when we have updated the cache key to base on raw-IP + URI this cacheable limit can go. +*/ + // add a security context (the original dst IP) to the cache key + // so that only clients sharing this same suspect request can get to it + char ipbuf[MAX_IPSTRLEN+1]; + http->request->storeSecurityKey = http->getConn()->clientConnection->local.toUrl(ipbuf, sizeof(ipbuf)-1); + http->request->flags.hierarchical = false; // MUST NOT pass to peers (for now) // XXX: when we have sorted out the best way to relay requests properly to peers this hierarchical limit can go. http->doCallouts(); return; } debugs(85, DBG_IMPORTANT, "SECURITY ALERT: Host header forgery detected on " << http->getConn()->clientConnection << " (" << A << " does not match " << B << ")"); debugs(85, DBG_IMPORTANT, "SECURITY ALERT: By user agent: " << http->request->header.getStr(HDR_USER_AGENT)); debugs(85, DBG_IMPORTANT, "SECURITY ALERT: on URL: " << urlCanonical(http->request)); // IP address validation for Host: failed. reject the connection. clientStreamNode *node = (clientStreamNode *)http->client_stream.tail->prev->data; clientReplyContext *repContext = dynamic_cast(node->data.getRaw()); assert (repContext); repContext->setReplyToError(ERR_CONFLICT_HOST, Http::scConflict, http->request->method, NULL, http->getConn()->clientConnection->remote, http->request, NULL, === modified file 'src/http.cc' --- src/http.cc 2014-04-27 07:59:17 +0000 +++ src/http.cc 2014-05-11 10:15:27 +0000 @@ -239,57 +239,59 @@ default: #if QUESTIONABLE /* * Any 2xx response should eject previously cached entities... */ if (status >= 200 && status < 300) remove = 1; #endif break; } if (!remove && !forbidden) return; assert(e->mem_obj); + // storeGetPublicByRequest() returns best-match from a set of entries. + // So we loop to invalidate all the entries it can find. + do { + if (e->mem_obj->request) pe = storeGetPublicByRequest(e->mem_obj->request); else pe = storeGetPublic(e->mem_obj->storeId(), e->mem_obj->method); if (pe != NULL) { assert(e != pe); #if USE_HTCP neighborsHtcpClear(e, NULL, e->mem_obj->request, e->mem_obj->method, HTCP_CLR_INVALIDATION); #endif pe->release(); } + } while (pe != NULL); - /** \par - * Also remove any cached HEAD response in case the object has - * changed. - */ + // also remove any cached HEAD response in case the object has changed if (e->mem_obj->request) pe = storeGetPublicByRequestMethod(e->mem_obj->request, Http::METHOD_HEAD); else pe = storeGetPublic(e->mem_obj->storeId(), Http::METHOD_HEAD); if (pe != NULL) { assert(e != pe); #if USE_HTCP neighborsHtcpClear(e, NULL, e->mem_obj->request, HttpRequestMethod(Http::METHOD_HEAD), HTCP_CLR_INVALIDATION); #endif pe->release(); } } void HttpStateData::processSurrogateControl(HttpReply *reply) { if (request->flags.accelerated && reply->surrogate_control) { HttpHdrScTarget *sctusable = reply->surrogate_control->getMergedTarget(Config.Accel.surrogate_id); === modified file 'src/store.cc' --- src/store.cc 2014-02-21 10:46:19 +0000 +++ src/store.cc 2014-05-11 10:17:51 +0000 @@ -594,41 +594,48 @@ StoreEntry::getPublic (StoreClient *aClient, const char *uri, const HttpRequestMethod& method) { assert (aClient); StoreEntry *result = storeGetPublic (uri, method); if (!result) result = NullStoreEntry::getInstance(); aClient->created (result); } StoreEntry * storeGetPublic(const char *uri, const HttpRequestMethod& method) { return Store::Root().get(storeKeyPublic(uri, method)); } StoreEntry * storeGetPublicByRequestMethod(HttpRequest * req, const HttpRequestMethod& method) { - return Store::Root().get(storeKeyPublicByRequestMethod(req, method)); + // prefer a general-access variant if one is available + StoreEntry *e = Store::Root().get(storeKeyPublicByRequestMethod(req, method)); + + // use a private variant if necessary + if (!e && req->storeSecurityKey.size() > 0) + e = Store::Root().get(storeKeyPublicByRequestMethod(req, method, true)); + + return e; } StoreEntry * storeGetPublicByRequest(HttpRequest * req) { StoreEntry *e = storeGetPublicByRequestMethod(req, req->method); if (e == NULL && req->method == Http::METHOD_HEAD) /* We can generate a HEAD reply from a cached GET object */ e = storeGetPublicByRequestMethod(req, Http::METHOD_GET); return e; } static int getKeyCounter(void) { static int key_counter = 0; if (++key_counter < 0) @@ -752,51 +759,51 @@ if (vary.size() > 0) { /* Again, we own this structure layout */ rep->header.putStr(HDR_X_ACCELERATOR_VARY, vary.termedBuf()); vary.clean(); } #endif pe->replaceHttpReply(rep, false); // no write until key is public pe->timestampsSet(); pe->makePublic(); pe->startWriting(); // after makePublic() pe->complete(); pe->unlock("StoreEntry::setPublicKey+Vary"); } - newkey = storeKeyPublicByRequest(mem_obj->request); + newkey = storeKeyPublicByRequest(mem_obj->request, true); } else newkey = storeKeyPublic(mem_obj->storeId(), mem_obj->method); if (StoreEntry *e2 = (StoreEntry *)hash_lookup(store_table, newkey)) { debugs(20, 3, "Making old " << *e2 << " private."); e2->setPrivateKey(); e2->release(); if (mem_obj->request) - newkey = storeKeyPublicByRequest(mem_obj->request); + newkey = storeKeyPublicByRequest(mem_obj->request, true); else newkey = storeKeyPublic(mem_obj->storeId(), mem_obj->method); } if (key) hashDelete(); EBIT_CLR(flags, KEY_PRIVATE); hashInsert(newkey); if (swap_filen > -1) storeDirSwapLog(this, SWAP_LOG_ADD); } StoreEntry * storeCreatePureEntry(const char *url, const char *log_url, const RequestFlags &flags, const HttpRequestMethod& method) { StoreEntry *e = NULL; debugs(20, 3, "storeCreateEntry: '" << url << "'"); === modified file 'src/store_key_md5.cc' --- src/store_key_md5.cc 2013-06-27 21:04:01 +0000 +++ src/store_key_md5.cc 2014-05-11 10:02:58 +0000 @@ -115,61 +115,66 @@ SquidMD5Update(&M, (unsigned char *) &method, sizeof(method)); SquidMD5Update(&M, (unsigned char *) url, strlen(url)); SquidMD5Final(digest, &M); return digest; } const cache_key * storeKeyPublic(const char *url, const HttpRequestMethod& method) { static cache_key digest[SQUID_MD5_DIGEST_LENGTH]; unsigned char m = (unsigned char) method.id(); SquidMD5_CTX M; SquidMD5Init(&M); SquidMD5Update(&M, &m, sizeof(m)); SquidMD5Update(&M, (unsigned char *) url, strlen(url)); SquidMD5Final(digest, &M); return digest; } const cache_key * -storeKeyPublicByRequest(HttpRequest * request) +storeKeyPublicByRequest(HttpRequest * request, bool useSecurityContext) { - return storeKeyPublicByRequestMethod(request, request->method); + return storeKeyPublicByRequestMethod(request, request->method, useSecurityContext); } const cache_key * -storeKeyPublicByRequestMethod(HttpRequest * request, const HttpRequestMethod& method) +storeKeyPublicByRequestMethod(HttpRequest * request, const HttpRequestMethod& method, bool useSecurityContext) { static cache_key digest[SQUID_MD5_DIGEST_LENGTH]; unsigned char m = (unsigned char) method.id(); const char *url = request->storeId(); /* storeId returns the right storeID\canonical URL for the md5 calc */ SquidMD5_CTX M; SquidMD5Init(&M); SquidMD5Update(&M, &m, sizeof(m)); SquidMD5Update(&M, (unsigned char *) url, strlen(url)); if (request->vary_headers) { SquidMD5Update(&M, (unsigned char *) request->vary_headers, strlen(request->vary_headers)); debugs(20, 3, "updating public key by vary headers: " << request->vary_headers << " for: " << url); } + if (useSecurityContext && request->storeSecurityKey.size() > 0) { + SquidMD5Update(&M, (unsigned char *) request->storeSecurityKey.rawBuf(), request->storeSecurityKey.size()); + debugs(20, 3, "updating public key by security context: " << request->storeSecurityKey << " for: " << url); + } + SquidMD5Final(digest, &M); return digest; } cache_key * storeKeyDup(const cache_key * key) { cache_key *dup = (cache_key *)memAllocate(MEM_MD5_DIGEST); memcpy(dup, key, SQUID_MD5_DIGEST_LENGTH); return dup; } cache_key * storeKeyCopy(cache_key * dst, const cache_key * src) { memcpy(dst, src, SQUID_MD5_DIGEST_LENGTH); return dst; } === modified file 'src/store_key_md5.h' --- src/store_key_md5.h 2012-09-21 14:57:30 +0000 +++ src/store_key_md5.h 2014-05-11 08:54:29 +0000 @@ -28,31 +28,31 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. * */ #ifndef SQUID_STORE_KEY_MD5_H_ #define SQUID_STORE_KEY_MD5_H_ #include "hash.h" #include "typedefs.h" class HttpRequestMethod; class HttpRequest; cache_key *storeKeyDup(const cache_key *); cache_key *storeKeyCopy(cache_key *, const cache_key *); void storeKeyFree(const cache_key *); const cache_key *storeKeyScan(const char *); const char *storeKeyText(const cache_key *); const cache_key *storeKeyPublic(const char *, const HttpRequestMethod&); -const cache_key *storeKeyPublicByRequest(HttpRequest *); -const cache_key *storeKeyPublicByRequestMethod(HttpRequest *, const HttpRequestMethod&); +const cache_key *storeKeyPublicByRequest(HttpRequest *, bool useSecurityContext = false); +const cache_key *storeKeyPublicByRequestMethod(HttpRequest *, const HttpRequestMethod&, bool useSecurityContext = false); const cache_key *storeKeyPrivate(const char *, const HttpRequestMethod&, int); int storeKeyHashBuckets(int); int storeKeyNull(const cache_key *); void storeKeyInit(void); extern HASHHASH storeKeyHashHash; extern HASHCMP storeKeyHashCmp; #endif /* SQUID_STORE_KEY_MD5_H_ */