]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/client_side_request.cc
Merge form trunk
[thirdparty/squid.git] / src / client_side_request.cc
index aa2d608b47c9fba4213d212c1b2b8867ad5283c1..4608f180c052779e73bc7f4cb157503c225e9b2e 100644 (file)
  */
 
 #include "squid.h"
-#include "clientStream.h"
-#include "client_side_request.h"
-#include "auth/UserRequest.h"
-#include "HttpRequest.h"
-#include "ProtoPort.h"
 #include "acl/FilledChecklist.h"
 #include "acl/Gadgets.h"
-#include "client_side.h"
-#include "client_side_reply.h"
-#include "Store.h"
-#include "HttpReply.h"
-#include "MemObject.h"
-#include "ClientRequestContext.h"
-#include "SquidTime.h"
-#include "wordlist.h"
-#include "inet_pton.h"
-#include "fde.h"
-
 #if USE_ADAPTATION
 #include "adaptation/AccessCheck.h"
 #include "adaptation/Iterator.h"
 #if ICAP_CLIENT
 #include "adaptation/icap/History.h"
 #endif
-//static void adaptationAclCheckDoneWrapper(Adaptation::ServicePointer service, void *data);
 #endif
-
+#include "auth/UserRequest.h"
+#include "clientStream.h"
+#include "client_side.h"
+#include "client_side_reply.h"
+#include "client_side_request.h"
+#include "ClientRequestContext.h"
+#include "comm/Connection.h"
+#include "compat/inet_pton.h"
+#include "fde.h"
+#include "HttpReply.h"
+#include "HttpRequest.h"
+#include "ip/QosConfig.h"
+#include "MemObject.h"
+#include "ProtoPort.h"
+#include "Store.h"
+#include "SquidTime.h"
+#include "wordlist.h"
+#include "err_detail_type.h"
 
 
 #if LINGERING_CLOSE
@@ -162,6 +162,7 @@ ClientHttpRequest::ClientHttpRequest(ConnStateData * aConn) :
 {
     start_time = current_time;
     setConn(aConn);
+    clientConnection = aConn->clientConn;
     dlinkAdd(this, &active, &ClientActiveRequests);
 #if USE_ADAPTATION
     request_satisfaction_mode = false;
@@ -276,6 +277,8 @@ ClientHttpRequest::~ClientHttpRequest()
     if (calloutContext)
         delete calloutContext;
 
+    clientConnection = NULL;
+
     if (conn_)
         cbdataReferenceDone(conn_);
 
@@ -285,18 +288,21 @@ ClientHttpRequest::~ClientHttpRequest()
     PROF_stop(httpRequestFree);
 }
 
-/* Create a request and kick it off */
-/*
+/**
+ * Create a request and kick it off
+ *
+ * \retval 0     success
+ * \retval -1    failure
+ *
  * TODO: Pass in the buffers to be used in the inital Read request, as they are
  * determined by the user
  */
-int                            /* returns nonzero on failure */
+int
 clientBeginRequest(const HttpRequestMethod& method, char const *url, CSCB * streamcallback,
                    CSD * streamdetach, ClientStreamData streamdata, HttpHeader const *header,
                    char *tailbuf, size_t taillen)
 {
     size_t url_sz;
-    HttpVersion http_ver (1, 0);
     ClientHttpRequest *http = new ClientHttpRequest(NULL);
     HttpRequest *request;
     StoreIOBuffer tempBuffer;
@@ -325,7 +331,7 @@ clientBeginRequest(const HttpRequestMethod& method, char const *url, CSCB * stre
     }
 
     /*
-     * now update the headers in request with our supplied headers. urLParse
+     * now update the headers in request with our supplied headers. urlParse
      * should return a blank header set, but we use Update to be sure of
      * correctness.
      */
@@ -364,6 +370,8 @@ clientBeginRequest(const HttpRequestMethod& method, char const *url, CSCB * stre
 
     request->my_addr.SetPort(0);
 
+    /* Our version is HTTP/1.1 */
+    HttpVersion http_ver(1,1);
     request->http_ver = http_ver;
 
     http->request = HTTPMSGLOCK(request);
@@ -399,23 +407,30 @@ ClientRequestContext::httpStateIsValid()
 
 #if FOLLOW_X_FORWARDED_FOR
 /**
- * clientFollowXForwardedForCheck() checks the indirect_client_addr
+ * clientFollowXForwardedForCheck() checks the content of X-Forwarded-For:
  * against the followXFF ACL, or cleans up and passes control to
  * clientAccessCheck().
+ *
+ * The trust model here is a little ambiguous. So to clarify the logic:
+ * - we may always use the direct client address as the client IP.
+ * - these trust tests merey tell whether we trust given IP enough to believe the
+ *   IP string which it appended to the X-Forwarded-For: header.
+ * - if at any point we don't trust what an IP adds we stop looking.
+ * - at that point the current contents of indirect_client_addr are the value set
+ *   by the last previously trusted IP.
+ * ++ indirect_client_addr contains the remote direct client from the trusted peers viewpoint.
  */
-
 static void
 clientFollowXForwardedForCheck(int answer, void *data)
 {
     ClientRequestContext *calloutContext = (ClientRequestContext *) data;
-    ClientHttpRequest *http = NULL;
-    HttpRequest *request = NULL;
 
     if (!calloutContext->httpStateIsValid())
         return;
 
-    http = calloutContext->http;
-    request = http->request;
+    ClientHttpRequest *http = calloutContext->http;
+    HttpRequest *request = http->request;
+
     /*
      * answer should be be ACCESS_ALLOWED or ACCESS_DENIED if we are
      * called as a result of ACL checks, or -1 if we are called when
@@ -423,16 +438,15 @@ clientFollowXForwardedForCheck(int answer, void *data)
      */
     if (answer == ACCESS_ALLOWED &&
             request->x_forwarded_for_iterator.size () != 0) {
+
         /*
-        * The IP address currently in request->indirect_client_addr
-        * is trusted to use X-Forwarded-For.  Remove the last
-        * comma-delimited element from x_forwarded_for_iterator and use
-        * it to to replace indirect_client_addr, then repeat the cycle.
-        */
+         * Remove the last comma-delimited element from the
+         * x_forwarded_for_iterator and use it to repeat the cycle.
+         */
         const char *p;
         const char *asciiaddr;
         int l;
-        struct in_addr addr;
+        Ip::Address addr;
         p = request->x_forwarded_for_iterator.termedBuf();
         l = request->x_forwarded_for_iterator.size();
 
@@ -454,20 +468,15 @@ clientFollowXForwardedForCheck(int answer, void *data)
         while (l > 0 && ! (p[l-1] == ',' || xisspace(p[l-1])))
             l--;
         asciiaddr = p+l;
-        if (xinet_pton(AF_INET, asciiaddr, &addr) != 0) {
+        if ((addr = asciiaddr)) {
             request->indirect_client_addr = addr;
             request->x_forwarded_for_iterator.cut(l);
-            if (! Config.onoff.acl_uses_indirect_client) {
-                /*
-                * If acl_uses_indirect_client is off, then it's impossible
-                * to follow more than one level of X-Forwarded-For.
-                */
-                request->x_forwarded_for_iterator.clean();
+            calloutContext->acl_checklist = clientAclChecklistCreate(Config.accessList.followXFF, http);
+            if (!Config.onoff.acl_uses_indirect_client) {
+                /* override the default src_addr tested if we have to go deeper than one level into XFF */
+                Filled(calloutContext->acl_checklist)->src_addr = request->indirect_client_addr;
             }
-            calloutContext->acl_checklist =
-                clientAclChecklistCreate(Config.accessList.followXFF, http);
-            calloutContext->acl_checklist->
-            nonBlockingCheck(clientFollowXForwardedForCheck, data);
+            calloutContext->acl_checklist->nonBlockingCheck(clientFollowXForwardedForCheck, data);
             return;
         }
     } /*if (answer == ACCESS_ALLOWED &&
@@ -485,15 +494,8 @@ clientFollowXForwardedForCheck(int answer, void *data)
     request->x_forwarded_for_iterator.clean();
     request->flags.done_follow_x_forwarded_for = 1;
 
-    /* If follow XFF is denied, we reset the indirect_client_addr
-       to the direct client. Thats the one we are configured to check for */
-    if (answer == ACCESS_DENIED) {
-        request->indirect_client_addr = request->client_addr;
-    }
-    /* on a failure, leave it as undefined state ?? */
-    else if (answer != ACCESS_ALLOWED) {
-        debugs(28, DBG_CRITICAL, "Follow X-Forwarded-For encountered an error. Ignoring address: " << request->indirect_client_addr );
-        request->indirect_client_addr = request->client_addr;
+    if (answer != ACCESS_ALLOWED && answer != ACCESS_DENIED) {
+        debugs(28, DBG_CRITICAL, "ERROR: Processing X-Forwarded-For. Stopping at IP address: " << request->indirect_client_addr );
     }
 
     /* process actual access ACL as normal. */
@@ -509,9 +511,17 @@ ClientRequestContext::clientAccessCheck()
     if (!http->request->flags.done_follow_x_forwarded_for &&
             Config.accessList.followXFF &&
             http->request->header.has(HDR_X_FORWARDED_FOR)) {
-        http->request->x_forwarded_for_iterator =
-            http->request->header.getList(HDR_X_FORWARDED_FOR);
-        clientFollowXForwardedForCheck(ACCESS_ALLOWED, this);
+
+        /* we always trust the direct client address for actual use */
+        http->request->indirect_client_addr = http->request->client_addr;
+        http->request->indirect_client_addr.SetPort(0);
+
+        /* setup the XFF iterator for processing */
+        http->request->x_forwarded_for_iterator = http->request->header.getList(HDR_X_FORWARDED_FOR);
+
+        /* begin by checking to see if we trust direct client enough to walk XFF */
+        acl_checklist = clientAclChecklistCreate(Config.accessList.followXFF, http);
+        acl_checklist->nonBlockingCheck(clientFollowXForwardedForCheck, this);
         return;
     }
 #endif /* FOLLOW_X_FORWARDED_FOR */
@@ -525,6 +535,23 @@ ClientRequestContext::clientAccessCheck()
     }
 }
 
+/**
+ * Identical in operation to clientAccessCheck() but performed later using different configured ACL list.
+ * The default here is to allow all. Since the earlier http_access should do a default deny all.
+ * This check is just for a last-minute denial based on adapted request headers.
+ */
+void
+ClientRequestContext::clientAccessCheck2()
+{
+    if (Config.accessList.adapted_http) {
+        acl_checklist = clientAclChecklistCreate(Config.accessList.adapted_http, http);
+        acl_checklist->nonBlockingCheck(clientAccessCheckDoneWrapper, this);
+    } else {
+        debugs(85, 2, HERE << "No adapted_http_access configuration. default: ALLOW");
+        clientAccessCheckDone(ACCESS_ALLOWED);
+    }
+}
+
 void
 clientAccessCheckDoneWrapper(int answer, void *data)
 {
@@ -595,14 +622,14 @@ ClientRequestContext::clientAccessCheckDone(int answer)
         clientStreamNode *node = (clientStreamNode *)http->client_stream.tail->prev->data;
         clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
         assert (repContext);
-        IpAddress tmpnoaddr;
+        Ip::Address tmpnoaddr;
         tmpnoaddr.SetNoAddr();
         repContext->setReplyToError(page_id, status,
                                     http->request->method, NULL,
-                                    http->getConn() != NULL ? http->getConn()->peer : tmpnoaddr,
+                                    http->getConn() != NULL ? http->getConn()->clientConn->remote : tmpnoaddr,
                                     http->request,
                                     NULL,
-                                    http->getConn() != NULL && http->getConn()->auth_user_request ?
+                                    http->getConn() != NULL && http->getConn()->auth_user_request != NULL ?
                                     http->getConn()->auth_user_request : http->request->auth_user_request);
 
         node = (clientStreamNode *)http->client_stream.tail->data;
@@ -642,7 +669,8 @@ ClientRequestContext::adaptationAclCheckDone(Adaptation::ServiceGroupPointer g)
         if (http->getConn() != NULL) {
             ih->rfc931 = http->getConn()->rfc931;
 #if USE_SSL
-            ih->ssluser = sslGetUserEmail(fd_table[http->getConn()->fd].ssl);
+            assert(http->getConn()->clientConn != NULL);
+            ih->ssluser = sslGetUserEmail(fd_table[http->getConn()->clientConn->fd].ssl);
 #endif
         }
         ih->log_uri = http->log_uri;
@@ -803,10 +831,7 @@ clientInterpretRequestHeaders(ClientHttpRequest * http)
     HttpRequest *request = http->request;
     HttpHeader *req_hdr = &request->header;
     int no_cache = 0;
-#if !(USE_SQUID_ESI) || defined(USE_USERAGENT_LOG) || defined(USE_REFERER_LOG)
-
     const char *str;
-#endif
 
     request->imslen = -1;
     request->ims = req_hdr->getTime(HDR_IF_MODIFIED_SINCE);
@@ -814,44 +839,39 @@ clientInterpretRequestHeaders(ClientHttpRequest * http)
     if (request->ims > 0)
         request->flags.ims = 1;
 
-#if USE_SQUID_ESI
-    /*
-     * We ignore Cache-Control as per the Edge Architecture Section 3. See
-     * www.esi.org for more information.
-     */
-#else
-
-    if (req_hdr->has(HDR_PRAGMA)) {
-        String s = req_hdr->getList(HDR_PRAGMA);
+    if (!request->flags.ignore_cc) {
+        if (req_hdr->has(HDR_PRAGMA)) {
+            String s = req_hdr->getList(HDR_PRAGMA);
 
-        if (strListIsMember(&s, "no-cache", ','))
-            no_cache++;
+            if (strListIsMember(&s, "no-cache", ','))
+                no_cache++;
 
-        s.clean();
-    }
+            s.clean();
+        }
 
-    if (request->cache_control)
-        if (EBIT_TEST(request->cache_control->mask, CC_NO_CACHE))
-            no_cache++;
+        if (request->cache_control)
+            if (EBIT_TEST(request->cache_control->mask, CC_NO_CACHE))
+                no_cache++;
 
-    /*
-    * Work around for supporting the Reload button in IE browsers when Squid
-    * is used as an accelerator or transparent proxy, by turning accelerated
-    * IMS request to no-cache requests. Now knows about IE 5.5 fix (is
-    * actually only fixed in SP1, but we can't tell whether we are talking to
-    * SP1 or not so all 5.5 versions are treated 'normally').
-    */
-    if (Config.onoff.ie_refresh) {
-        if (http->flags.accel && request->flags.ims) {
-            if ((str = req_hdr->getStr(HDR_USER_AGENT))) {
-                if (strstr(str, "MSIE 5.01") != NULL)
-                    no_cache++;
-                else if (strstr(str, "MSIE 5.0") != NULL)
-                    no_cache++;
-                else if (strstr(str, "MSIE 4.") != NULL)
-                    no_cache++;
-                else if (strstr(str, "MSIE 3.") != NULL)
-                    no_cache++;
+        /*
+        * Work around for supporting the Reload button in IE browsers when Squid
+        * is used as an accelerator or transparent proxy, by turning accelerated
+        * IMS request to no-cache requests. Now knows about IE 5.5 fix (is
+        * actually only fixed in SP1, but we can't tell whether we are talking to
+        * SP1 or not so all 5.5 versions are treated 'normally').
+        */
+        if (Config.onoff.ie_refresh) {
+            if (http->flags.accel && request->flags.ims) {
+                if ((str = req_hdr->getStr(HDR_USER_AGENT))) {
+                    if (strstr(str, "MSIE 5.01") != NULL)
+                        no_cache++;
+                    else if (strstr(str, "MSIE 5.0") != NULL)
+                        no_cache++;
+                    else if (strstr(str, "MSIE 4.") != NULL)
+                        no_cache++;
+                    else if (strstr(str, "MSIE 3.") != NULL)
+                        no_cache++;
+                }
             }
         }
     }
@@ -860,9 +880,8 @@ clientInterpretRequestHeaders(ClientHttpRequest * http)
         no_cache++;
     }
 
-#endif
     if (no_cache) {
-#if HTTP_VIOLATIONS
+#if USE_HTTP_VIOLATIONS
 
         if (Config.onoff.reload_into_ims)
             request->flags.nocache_hack = 1;
@@ -876,7 +895,9 @@ clientInterpretRequestHeaders(ClientHttpRequest * http)
 
     /* ignore range header in non-GETs or non-HEADs */
     if (request->method == METHOD_GET || request->method == METHOD_HEAD) {
-        request->range = req_hdr->getRange();
+        // XXX: initialize if we got here without HttpRequest::parseHeader()
+        if (!request->range)
+            request->range = req_hdr->getRange();
 
         if (request->range) {
             request->flags.range = 1;
@@ -900,6 +921,7 @@ clientInterpretRequestHeaders(ClientHttpRequest * http)
     else {
         req_hdr->delById(HDR_RANGE);
         req_hdr->delById(HDR_REQUEST_RANGE);
+        delete request->range;
         request->range = NULL;
     }
 
@@ -925,7 +947,7 @@ clientInterpretRequestHeaders(ClientHttpRequest * http)
             request->flags.loopdetect = 1;
         }
 
-#if FORW_VIA_DB
+#if USE_FORW_VIA_DB
         fvdbCountVia(s.termedBuf());
 
 #endif
@@ -948,7 +970,7 @@ clientInterpretRequestHeaders(ClientHttpRequest * http)
         logReferer(fqdnFromAddr(http->getConn()->log_addr), str, http->log_uri);
 
 #endif
-#if FORW_VIA_DB
+#if USE_FORW_VIA_DB
 
     if (req_hdr->has(HDR_X_FORWARDED_FOR)) {
         String s = req_hdr->getList(HDR_X_FORWARDED_FOR);
@@ -957,9 +979,6 @@ clientInterpretRequestHeaders(ClientHttpRequest * http)
     }
 
 #endif
-    if (request->method == METHOD_TRACE || request->method == METHOD_OPTIONS) {
-        request->max_forwards = req_hdr->getInt64(HDR_MAX_FORWARDS);
-    }
 
     request->flags.cachable = http->request->cacheable();
 
@@ -1007,11 +1026,18 @@ ClientRequestContext::clientRedirectDone(char *result)
             if ((t = strchr(result, ':')) != NULL) {
                 http->redirect.status = status;
                 http->redirect.location = xstrdup(t + 1);
+                // TODO: validate the URL produced here is RFC 2616 compliant absolute URI
             } else {
-                debugs(85, 1, "clientRedirectDone: bad input: " << result);
+                if (old_request->http_ver < HttpVersion(1,1))
+                    debugs(85, DBG_CRITICAL, "ERROR: URL-rewrite produces invalid 302 redirect Location: " << result);
+                else
+                    debugs(85, DBG_CRITICAL, "ERROR: URL-rewrite produces invalid 303 redirect Location: " << result);
             }
-        } else if (strcmp(result, http->uri))
-            new_request = HttpRequest::CreateFromUrlAndMethod(result, old_request->method);
+        } else if (strcmp(result, http->uri)) {
+            if (!(new_request = HttpRequest::CreateFromUrlAndMethod(result, old_request->method)))
+                debugs(85, DBG_CRITICAL, "ERROR: URL-rewrite produces invalid request: " <<
+                       old_request->method << " " << result << " HTTP/1.1");
+        }
     }
 
     if (new_request) {
@@ -1026,11 +1052,7 @@ ClientRequestContext::clientRedirectDone(char *result)
         new_request->my_addr = old_request->my_addr;
         new_request->flags = old_request->flags;
         new_request->flags.redirected = 1;
-
-        if (old_request->auth_user_request) {
-            new_request->auth_user_request = old_request->auth_user_request;
-            AUTHUSERREQUESTLOCK(new_request->auth_user_request, "new request");
-        }
+        new_request->auth_user_request = old_request->auth_user_request;
 
         if (old_request->body_pipe != NULL) {
             new_request->body_pipe = old_request->body_pipe;
@@ -1048,8 +1070,8 @@ ClientRequestContext::clientRedirectDone(char *result)
 
     /* FIXME PIPELINE: This is innacurate during pipelining */
 
-    if (http->getConn() != NULL)
-        fd_note(http->getConn()->fd, http->uri);
+    if (http->getConn() != NULL && Comm::IsConnOpen(http->getConn()->clientConn))
+        fd_note(http->getConn()->clientConn->fd, http->uri);
 
     assert(http->uri);
 
@@ -1109,6 +1131,7 @@ ClientHttpRequest::processRequest()
 
     if (request->method == METHOD_CONNECT && !redirect.status) {
         logType = LOG_TCP_MISS;
+        getConn()->stopReading(); // tunnels read for themselves
         tunnelStart(this, &out.size, &al.http.code);
         return;
     }
@@ -1150,7 +1173,7 @@ ClientHttpRequest::sslBumpNeeded() const
 
 // called when comm_write has completed
 static void
-SslBumpEstablish(int, char *, size_t, comm_err_t errflag, int, void *data)
+SslBumpEstablish(const Comm::ConnectionPointer &, char *, size_t, comm_err_t errflag, int, void *data)
 {
     ClientHttpRequest *r = static_cast<ClientHttpRequest*>(data);
     debugs(85, 5, HERE << "responded to CONNECT: " << r << " ? " << errflag);
@@ -1177,17 +1200,13 @@ ClientHttpRequest::sslBumpEstablish(comm_err_t errflag)
 void
 ClientHttpRequest::sslBumpStart()
 {
-    debugs(85, 5, HERE << "ClientHttpRequest::sslBumpStart");
-
+    debugs(85, 5, HERE << "Confirming CONNECT tunnel on FD " << getConn()->clientConn);
     // send an HTTP 200 response to kick client SSL negotiation
-    const int fd = getConn()->fd;
-    debugs(33, 7, HERE << "Confirming CONNECT tunnel on FD " << fd);
+    debugs(33, 7, HERE << "Confirming CONNECT tunnel on FD " << getConn()->clientConn);
 
     // TODO: Unify with tunnel.cc and add a Server(?) header
-    static const char *const conn_established =
-        "HTTP/1.0 200 Connection established\r\n\r\n";
-    comm_write(fd, conn_established, strlen(conn_established),
-               &SslBumpEstablish, this, NULL);
+    static const char *const conn_established = "HTTP/1.0 200 Connection established\r\n\r\n";
+    comm_write(getConn()->clientConn, conn_established, strlen(conn_established), &SslBumpEstablish, this, NULL);
 }
 
 #endif
@@ -1256,13 +1275,18 @@ ClientHttpRequest::loggingEntry(StoreEntry *newEntry)
  * the callout.  This is strictly for convenience.
  */
 
-extern int aclMapTOS (acl_tos * head, ACLChecklist * ch);
+extern tos_t aclMapTOS (acl_tos * head, ACLChecklist * ch);
+extern nfmark_t aclMapNfmark (acl_nfmark * head, ACLChecklist * ch);
 
 void
 ClientHttpRequest::doCallouts()
 {
     assert(calloutContext);
 
+    /*Save the original request for logging purposes*/
+    if (!calloutContext->http->al.request)
+        calloutContext->http->al.request = HTTPMSGLOCK(request);
+
     if (!calloutContext->http_access_done) {
         debugs(83, 3, HERE << "Doing calloutContext->clientAccessCheck()");
         calloutContext->http_access_done = true;
@@ -1292,6 +1316,13 @@ ClientHttpRequest::doCallouts()
         }
     }
 
+    if (!calloutContext->adapted_http_access_done) {
+        debugs(83, 3, HERE << "Doing calloutContext->clientAccessCheck2()");
+        calloutContext->adapted_http_access_done = true;
+        calloutContext->clientAccessCheck2();
+        return;
+    }
+
     if (!calloutContext->interpreted_req_hdrs) {
         debugs(83, 3, HERE << "Doing clientInterpretRequestHeaders()");
         calloutContext->interpreted_req_hdrs = 1;
@@ -1308,15 +1339,27 @@ ClientHttpRequest::doCallouts()
         }
     }
 
-    if (!calloutContext->clientside_tos_done) {
-        calloutContext->clientside_tos_done = true;
-        if (getConn() != NULL) {
+    if (!calloutContext->tosToClientDone) {
+        calloutContext->tosToClientDone = true;
+        if (getConn() != NULL && Comm::IsConnOpen(getConn()->clientConn)) {
             ACLFilledChecklist ch(NULL, request, NULL);
             ch.src_addr = request->client_addr;
             ch.my_addr = request->my_addr;
-            int tos = aclMapTOS(Config.accessList.clientside_tos, &ch);
+            tos_t tos = aclMapTOS(Ip::Qos::TheConfig.tosToClient, &ch);
             if (tos)
-                comm_set_tos(getConn()->fd, tos);
+                Ip::Qos::setSockTos(getConn()->clientConn, tos);
+        }
+    }
+
+    if (!calloutContext->nfmarkToClientDone) {
+        calloutContext->nfmarkToClientDone = true;
+        if (getConn() != NULL && Comm::IsConnOpen(getConn()->clientConn)) {
+            ACLFilledChecklist ch(NULL, request, NULL);
+            ch.src_addr = request->client_addr;
+            ch.my_addr = request->my_addr;
+            nfmark_t mark = aclMapNfmark(Ip::Qos::TheConfig.nfmarkToClient, &ch);
+            if (mark)
+                Ip::Qos::setSockNfmark(getConn()->clientConn, mark);
         }
     }
 
@@ -1338,7 +1381,7 @@ ClientHttpRequest::doCallouts()
 #endif
 }
 
-#ifndef _USE_INLINE_
+#if !_USE_INLINE_
 #include "client_side_request.cci"
 #endif
 
@@ -1351,11 +1394,11 @@ ClientHttpRequest::startAdaptation(const Adaptation::ServiceGroupPointer &g)
     assert(!virginHeadSource);
     assert(!adaptedBodySource);
     virginHeadSource = initiateAdaptation(
-                           new Adaptation::Iterator(this, request, NULL, g));
+                           new Adaptation::Iterator(request, NULL, g));
 
     // we could try to guess whether we can bypass this adaptation
     // initiation failure, but it should not really happen
-    assert(virginHeadSource != NULL); // Must, really
+    Must(initiated(virginHeadSource));
 }
 
 void
@@ -1395,6 +1438,7 @@ ClientHttpRequest::noteAdaptationAnswer(HttpMsg *msg)
         request_satisfaction_mode = true;
         request_satisfaction_offset = 0;
         storeEntry()->replaceHttpReply(new_rep);
+        storeEntry()->timestampsSet();
 
         if (!adaptedBodySource) // no body
             storeEntry()->complete();
@@ -1413,7 +1457,7 @@ ClientHttpRequest::noteAdaptationQueryAbort(bool final)
 {
     clearAdaptation(virginHeadSource);
     assert(!adaptedBodySource);
-    handleAdaptationFailure(!final);
+    handleAdaptationFailure(ERR_DETAIL_ICAP_REQMOD_ABORT, !final);
 }
 
 void
@@ -1466,11 +1510,11 @@ ClientHttpRequest::noteBodyProducerAborted(BodyPipe::Pointer)
 {
     assert(!virginHeadSource);
     stopConsumingFrom(adaptedBodySource);
-    handleAdaptationFailure();
+    handleAdaptationFailure(ERR_DETAIL_ICAP_RESPMOD_CLT_SIDE_BODY);
 }
 
 void
-ClientHttpRequest::handleAdaptationFailure(bool bypassable)
+ClientHttpRequest::handleAdaptationFailure(int errDetail, bool bypassable)
 {
     debugs(85,3, HERE << "handleAdaptationFailure(" << bypassable << ")");
 
@@ -1494,15 +1538,17 @@ ClientHttpRequest::handleAdaptationFailure(bool bypassable)
     // The original author of the code also wanted to pass an errno to
     // setReplyToError, but it seems unlikely that the errno reflects the
     // true cause of the error at this point, so I did not pass it.
-    IpAddress noAddr;
+    Ip::Address noAddr;
     noAddr.SetNoAddr();
     ConnStateData * c = getConn();
     repContext->setReplyToError(ERR_ICAP_FAILURE, HTTP_INTERNAL_SERVER_ERROR,
                                 request->method, NULL,
-                                (c != NULL ? c->peer : noAddr), request, NULL,
-                                (c != NULL && c->auth_user_request ?
+                                (c != NULL ? c->clientConn->remote : noAddr), request, NULL,
+                                (c != NULL && c->auth_user_request != NULL ?
                                  c->auth_user_request : request->auth_user_request));
 
+    request->detailError(ERR_ICAP_FAILURE, errDetail);
+
     node = (clientStreamNode *)client_stream.tail->data;
     clientStreamRead(node, this, node->readBuffer);
 }