Ok, I've had time to clean this patch up... I'm not sure how half my patch went missing the last time I sent it - I was obviously having a bad day. :)

The attached patch adds a "spoof_client_ip" fast ACL to control whether TPROXY
requests have their source IP address spoofed by Squid.  The ACL
defaults to allow (i.e. the current normal behaviour), but using an ACL
that results in a deny result will disable spoofing for that request.

 Example config (disables spoofing for all requests):
     spoof_client_ip deny all

I've implemented the changes suggested by both Alex and Amos.

The patch also does a bit of code-cleanup:

1. The flags.spoofClientIp flag was a general "this is a TPROXY request"
flag, which was a bit confusing given the name of the flag.  So the
flags.spoofClientIp flag now only indicates whether we want to spoof the
source IP or not.

2. TPROXY requests now all set flags.interceptTproxy, irrespective of whether there is going to be any address spoofing.

--

 - Steve Hill
   Technical Director
   Opendium Limited     http://www.opendium.com

Direct contacts:
   Instant messager: xmpp:[email protected]
   Email:            [email protected]
   Phone:            sip:[email protected]

Sales / enquiries contacts:
   Email:            [email protected]
   Phone:            +44-844-9791439 / sip:[email protected]

Support contacts:
   Email:            [email protected]
   Phone:            +44-844-4844916 / sip:[email protected]
diff -urN squid-3.3.1.vanilla/src/acl/DestinationIp.cc squid-3.3.1/src/acl/DestinationIp.cc
--- squid-3.3.1.vanilla/src/acl/DestinationIp.cc	2013-02-09 07:30:01.000000000 +0000
+++ squid-3.3.1/src/acl/DestinationIp.cc	2013-02-25 16:25:17.000000000 +0000
@@ -54,7 +54,7 @@
     // Bypass of browser same-origin access control in intercepted communication
     // To resolve this we will force DIRECT and only to the original client destination.
     // In which case, we also need this ACL to accurately match the destination
-    if (Config.onoff.client_dst_passthru && (checklist->request->flags.intercepted || checklist->request->flags.spoofClientIp)) {
+    if (Config.onoff.client_dst_passthru && (checklist->request->flags.intercepted || checklist->request->flags.interceptTproxy)) {
         assert(checklist->conn() && checklist->conn()->clientConnection != NULL);
         return ACLIP::match(checklist->conn()->clientConnection->local);
     }
diff -urN squid-3.3.1.vanilla/src/anyp/PortCfg.cc squid-3.3.1/src/anyp/PortCfg.cc
--- squid-3.3.1.vanilla/src/anyp/PortCfg.cc	2013-02-09 07:30:01.000000000 +0000
+++ squid-3.3.1/src/anyp/PortCfg.cc	2013-02-25 16:25:17.000000000 +0000
@@ -58,7 +58,7 @@
         b->defaultsite = xstrdup(defaultsite);
 
     b->intercepted = intercepted;
-    b->spoof_client_ip = spoof_client_ip;
+    b->interceptTproxy = interceptTproxy;
     b->accel = accel;
     b->allow_direct = allow_direct;
     b->vhost = vhost;
diff -urN squid-3.3.1.vanilla/src/anyp/PortCfg.h squid-3.3.1/src/anyp/PortCfg.h
--- squid-3.3.1.vanilla/src/anyp/PortCfg.h	2013-02-09 07:30:01.000000000 +0000
+++ squid-3.3.1/src/anyp/PortCfg.h	2013-02-25 16:25:17.000000000 +0000
@@ -30,7 +30,7 @@
     char *defaultsite;         /* default web site */
 
     unsigned int intercepted:1;        /**< intercepting proxy port */
-    unsigned int spoof_client_ip:1;    /**< spoof client ip if possible */
+    unsigned int interceptTproxy:1;    /**< TPROXY port */
     unsigned int accel:1;              /**< HTTP accelerator */
     unsigned int allow_direct:1;       /**< Allow direct forwarding in accelerator mode */
     unsigned int vhost:1;              /**< uses host header */
diff -urN squid-3.3.1.vanilla/src/auth/Acl.cc squid-3.3.1/src/auth/Acl.cc
--- squid-3.3.1.vanilla/src/auth/Acl.cc	2013-02-09 07:30:01.000000000 +0000
+++ squid-3.3.1/src/auth/Acl.cc	2013-02-25 16:25:17.000000000 +0000
@@ -34,7 +34,7 @@
     } else if (request->flags.accelerated) {
         /* WWW authorization on accelerated requests */
         headertype = HDR_AUTHORIZATION;
-    } else if (request->flags.intercepted || request->flags.spoofClientIp) {
+    } else if (request->flags.intercepted || request->flags.interceptTproxy) {
         debugs(28, DBG_IMPORTANT, "NOTICE: Authentication not applicable on intercepted requests.");
         return ACCESS_DENIED;
     } else {
diff -urN squid-3.3.1.vanilla/src/cache_cf.cc squid-3.3.1/src/cache_cf.cc
--- squid-3.3.1.vanilla/src/cache_cf.cc	2013-02-09 07:30:01.000000000 +0000
+++ squid-3.3.1/src/cache_cf.cc	2013-02-25 16:25:17.000000000 +0000
@@ -3556,13 +3556,13 @@
     /* modes first */
 
     if (strcmp(token, "accel") == 0) {
-        if (s->intercepted || s->spoof_client_ip) {
+        if (s->intercepted || s->interceptTproxy) {
             debugs(3, DBG_CRITICAL, "FATAL: http(s)_port: Accelerator mode requires its own port. It cannot be shared with other modes.");
             self_destruct();
         }
         s->accel = s->vhost = 1;
     } else if (strcmp(token, "transparent") == 0 || strcmp(token, "intercept") == 0) {
-        if (s->accel || s->spoof_client_ip) {
+        if (s->accel || s->interceptTproxy) {
             debugs(3, DBG_CRITICAL, "FATAL: http(s)_port: Intercept mode requires its own interception port. It cannot be shared with other modes.");
             self_destruct();
         }
@@ -3584,7 +3584,7 @@
             debugs(3,DBG_CRITICAL, "FATAL: http(s)_port: TPROXY option requires its own interception port. It cannot be shared with other modes.");
             self_destruct();
         }
-        s->spoof_client_ip = 1;
+        s->interceptTproxy = 1;
         Ip::Interceptor.StartTransparency();
         /* Log information regarding the port modes under transparency. */
         debugs(3, DBG_IMPORTANT, "Starting IP Spoofing on port " << s->s);
@@ -3794,7 +3794,7 @@
 #if USE_SSL
     if (strcasecmp(protocol, "https") == 0) {
         /* ssl-bump on https_port configuration requires either tproxy or intercept, and vice versa */
-        const bool hijacked = s->spoof_client_ip || s->intercepted;
+        const bool hijacked = s->interceptTproxy || s->intercepted;
         if (s->sslBump && !hijacked) {
             debugs(3, DBG_CRITICAL, "FATAL: ssl-bump on https_port requires tproxy/intercept which is missing.");
             self_destruct();
@@ -3832,7 +3832,7 @@
     if (s->intercepted)
         storeAppendPrintf(e, " intercept");
 
-    else if (s->spoof_client_ip)
+    else if (s->interceptTproxy)
         storeAppendPrintf(e, " tproxy");
 
     else if (s->accel) {
diff -urN squid-3.3.1.vanilla/src/cf.data.pre squid-3.3.1/src/cf.data.pre
--- squid-3.3.1.vanilla/src/cf.data.pre	2013-02-09 07:30:01.000000000 +0000
+++ squid-3.3.1/src/cf.data.pre	2013-02-25 16:25:17.000000000 +0000
@@ -1133,6 +1133,26 @@
 	sources is required to prevent abuse of your proxy.
 DOC_END
 
+NAME: spoof_client_ip
+TYPE: acl_access
+LOC: Config.accessList.spoof_client_ip
+DEFAULT: none
+DEFAULT_DOC: allow
+DOC_START
+	Control client IP address spoofing of TPROXY traffic based on
+	defined access lists.
+
+	spoof_client_ip allow|deny [!]aclname ...
+
+	If there are no "spoof_client_ip" lines present, the default
+	is to "allow" spoofing of any suitable request.
+
+	Note that the cache_peer "no-tproxy" option overrides this ACL.
+
+	This clause supports fast acl types.
+	See http://wiki.squid-cache.org/SquidFaq/SquidAcl for details.
+DOC_END
+
 NAME: http_access
 TYPE: acl_access
 LOC: Config.accessList.http
@@ -2788,6 +2808,7 @@
 	
 	no-tproxy	Do not use the client-spoof TPROXY support when forwarding
 			requests to this peer. Use normal address selection instead.
+			This overrides the spoof_client_ip ACL.
 	
 	proxy-only	objects fetched from the peer will not be stored locally.
 	
diff -urN squid-3.3.1.vanilla/src/client_side.cc squid-3.3.1/src/client_side.cc
--- squid-3.3.1.vanilla/src/client_side.cc	2013-02-09 07:30:01.000000000 +0000
+++ squid-3.3.1/src/client_side.cc	2013-02-25 16:25:17.000000000 +0000
@@ -2674,7 +2674,14 @@
      */
     if (http->clientConnection != NULL) {
         request->flags.intercepted = ((http->clientConnection->flags & COMM_INTERCEPTION) != 0);
-        request->flags.spoofClientIp = ((http->clientConnection->flags & COMM_TRANSPARENT) != 0 ) ;
+        request->flags.interceptTproxy = ((http->clientConnection->flags & COMM_TRANSPARENT) != 0);
+        if (request->flags.interceptTproxy) {
+            if (Config.accessList.spoof_client_ip) {
+                ACLFilledChecklist *checklist = clientAclChecklistCreate(Config.accessList.spoof_client_ip, http);
+                request->flags.spoofClientIp = checklist->fastCheck() == ACCESS_ALLOWED;
+                delete checklist;
+            } else request->flags.spoofClientIp = 1;
+        } else request->flags.spoofClientIp = 0;
     }
 
     if (internalCheck(request->urlpath.termedBuf())) {
@@ -3575,7 +3582,7 @@
     else {
         char buf[MAX_IPSTRLEN];
         assert(bumpMode != Ssl::bumpNone && bumpMode != Ssl::bumpEnd);
-        HttpRequest *fakeRequest = new HttpRequest;
+        HttpRequest *fakeRequest = HTTPMSGLOCK(new HttpRequest);
         fakeRequest->SetHost(details->local.NtoA(buf, sizeof(buf)));
         fakeRequest->port = details->local.GetPort();
         fakeRequest->clientConnectionManager = connState;
@@ -3584,10 +3591,18 @@
         fakeRequest->indirect_client_addr = connState->clientConnection->remote;
 #endif
         fakeRequest->my_addr = connState->clientConnection->local;
-        fakeRequest->flags.spoofClientIp = ((connState->clientConnection->flags & COMM_TRANSPARENT) != 0 ) ;
+        fakeRequest->myportname = connState->port->name;
+        fakeRequest->flags.interceptTproxy = ((connState->clientConnection->flags & COMM_TRANSPARENT) != 0);
+        if (fakeRequest->flags.interceptTproxy) {
+            if (Config.accessList.spoof_client_ip) {
+                ACLFilledChecklist checklist(Config.accessList.spoof_client_ip, fakeRequest, NULL);
+                fakeRequest->flags.spoofClientIp = checklist.fastCheck() == ACCESS_ALLOWED;
+            } else fakeRequest->flags.spoofClientIp = 1;
+        } else fakeRequest->flags.spoofClientIp = 0;
         fakeRequest->flags.intercepted = ((connState->clientConnection->flags & COMM_INTERCEPTION) != 0);
         debugs(33, 4, HERE << details << " try to generate a Dynamic SSL CTX");
         connState->switchToHttps(fakeRequest, bumpMode);
+        HTTPMSGUNLOCK(fakeRequest);
     }
 }
 
@@ -3950,7 +3965,7 @@
         debugs(33, 5, HERE << "Error while bumping: " << sslConnectHostOrIp);
         Ip::Address intendedDest;
         intendedDest = sslConnectHostOrIp.termedBuf();
-        const bool isConnectRequest = !port->spoof_client_ip && !port->intercepted;
+        const bool isConnectRequest = !port->interceptTproxy && !port->intercepted;
 
         // Squid serves its own error page and closes, so we want
         // a CN that causes no additional browser errors. Possible
@@ -4031,7 +4046,7 @@
         //  then pass back when active so we can start a TcpAcceptor subscription.
         s->listenConn = new Comm::Connection;
         s->listenConn->local = s->s;
-        s->listenConn->flags = COMM_NONBLOCKING | (s->spoof_client_ip ? COMM_TRANSPARENT : 0) | (s->intercepted ? COMM_INTERCEPTION : 0);
+        s->listenConn->flags = COMM_NONBLOCKING | (s->interceptTproxy ? COMM_TRANSPARENT : 0) | (s->intercepted ? COMM_INTERCEPTION : 0);
 
         // setup the subscriptions such that new connections accepted by listenConn are handled by HTTP
         typedef CommCbFunPtrCallT<CommAcceptCbPtrFun> AcceptCall;
@@ -4085,7 +4100,7 @@
         // Fill out a Comm::Connection which IPC will open as a listener for us
         s->listenConn = new Comm::Connection;
         s->listenConn->local = s->s;
-        s->listenConn->flags = COMM_NONBLOCKING | (s->spoof_client_ip ? COMM_TRANSPARENT : 0) |
+        s->listenConn->flags = COMM_NONBLOCKING | (s->interceptTproxy ? COMM_TRANSPARENT : 0) |
                                (s->intercepted ? COMM_INTERCEPTION : 0);
 
         // setup the subscriptions such that new connections accepted by listenConn are handled by HTTPS
@@ -4118,7 +4133,7 @@
 
     debugs(1, DBG_IMPORTANT, "Accepting " <<
            (s->intercepted ? "NAT intercepted " : "") <<
-           (s->spoof_client_ip ? "TPROXY spoofing " : "") <<
+           (s->interceptTproxy ? "TPROXY intercepted " : "") <<
            (s->sslBump ? "SSL bumped " : "") <<
            (s->accel ? "reverse-proxy " : "")
            << FdNote(portTypeNote) << " connections at "
diff -urN squid-3.3.1.vanilla/src/client_side_request.cc squid-3.3.1/src/client_side_request.cc
--- squid-3.3.1.vanilla/src/client_side_request.cc	2013-02-09 07:30:01.000000000 +0000
+++ squid-3.3.1/src/client_side_request.cc	2013-02-25 16:25:17.000000000 +0000
@@ -664,7 +664,7 @@
     }
 
     debugs(85, 3, HERE << "validate host=" << host << ", port=" << port << ", portStr=" << (portStr?portStr:"NULL"));
-    if (http->request->flags.intercepted || http->request->flags.spoofClientIp) {
+    if (http->request->flags.intercepted || http->request->flags.interceptTproxy) {
         // verify the Host: port (if any) matches the apparent destination
         if (portStr && port != http->getConn()->clientConnection->local.GetPort()) {
             debugs(85, 3, HERE << "FAIL on validate port " << http->getConn()->clientConnection->local.GetPort() <<
@@ -924,7 +924,7 @@
     const wordlist *p = NULL;
 
     // intercepted requests MUST NOT (yet) be sent to peers unless verified
-    if (!request->flags.hostVerified && (request->flags.intercepted || request->flags.spoofClientIp))
+    if (!request->flags.hostVerified && (request->flags.intercepted || request->flags.interceptTproxy))
         return 0;
 
     /*
diff -urN squid-3.3.1.vanilla/src/format/Format.cc squid-3.3.1/src/format/Format.cc
--- squid-3.3.1.vanilla/src/format/Format.cc	2013-02-09 07:30:01.000000000 +0000
+++ squid-3.3.1/src/format/Format.cc	2013-02-25 16:25:17.000000000 +0000
@@ -383,7 +383,7 @@
         case LFT_LOCAL_LISTENING_IP: {
             // avoid logging a dash if we have reliable info
             const bool interceptedAtKnownPort = al->request ?
-                                                (al->request->flags.spoofClientIp ||
+                                                (al->request->flags.interceptTproxy ||
                                                  al->request->flags.intercepted) && al->cache.port :
                                                 false;
             if (interceptedAtKnownPort) {
diff -urN squid-3.3.1.vanilla/src/forward.cc squid-3.3.1/src/forward.cc
--- squid-3.3.1.vanilla/src/forward.cc	2013-02-09 07:30:01.000000000 +0000
+++ squid-3.3.1/src/forward.cc	2013-02-25 16:25:17.000000000 +0000
@@ -147,7 +147,7 @@
     // Bug 3243: CVE 2009-0801
     // Bypass of browser same-origin access control in intercepted communication
     // To resolve this we must force DIRECT and only to the original client destination.
-    const bool isIntercepted = request && !request->flags.redirected && (request->flags.intercepted || request->flags.spoofClientIp);
+    const bool isIntercepted = request && !request->flags.redirected && (request->flags.intercepted || request->flags.interceptTproxy);
     const bool useOriginalDst = Config.onoff.client_dst_passthru || (request && !request->flags.hostVerified);
     if (isIntercepted && useOriginalDst) {
         selectPeerForIntercepted();
@@ -729,7 +729,7 @@
             // For intercepted connections, set the host name to the server
             // certificate CN. Otherwise, we just hope that CONNECT is using
             // a user-entered address (a host name or a user-entered IP).
-            const bool isConnectRequest = !request->clientConnectionManager->port->spoof_client_ip &&
+            const bool isConnectRequest = !request->clientConnectionManager->port->interceptTproxy &&
                                           !request->clientConnectionManager->port->intercepted;
             if (request->flags.sslPeek && !isConnectRequest) {
                 if (X509 *srvX509 = errDetails->peerCert()) {
@@ -826,7 +826,7 @@
         // unless it was the CONNECT request with a user-typed address.
         const char *hostname = request->GetHost();
         const bool hostnameIsIp = request->GetHostIsNumeric();
-        const bool isConnectRequest = !request->clientConnectionManager->port->spoof_client_ip &&
+        const bool isConnectRequest = !request->clientConnectionManager->port->interceptTproxy &&
                                       !request->clientConnectionManager->port->intercepted;
         if (!request->flags.sslPeek || isConnectRequest)
             SSL_set_ex_data(ssl, ssl_ex_index_server, (void*)hostname);
diff -urN squid-3.3.1.vanilla/src/HttpRequest.cc squid-3.3.1/src/HttpRequest.cc
--- squid-3.3.1.vanilla/src/HttpRequest.cc	2013-02-09 07:30:01.000000000 +0000
+++ squid-3.3.1/src/HttpRequest.cc	2013-02-25 16:25:17.000000000 +0000
@@ -582,7 +582,7 @@
     // Because it failed verification, or someone bypassed the security tests
     // we cannot cache the reponse for sharing between clients.
     // TODO: update cache to store for particular clients only (going to same Host: and destination IP)
-    if (!flags.hostVerified && (flags.intercepted || flags.spoofClientIp))
+    if (!flags.hostVerified && (flags.intercepted || flags.interceptTproxy))
         return false;
 
     if (protocol == AnyP::PROTO_HTTP)
diff -urN squid-3.3.1.vanilla/src/peer_select.cc squid-3.3.1/src/peer_select.cc
--- squid-3.3.1.vanilla/src/peer_select.cc	2013-02-09 07:30:01.000000000 +0000
+++ squid-3.3.1/src/peer_select.cc	2013-02-25 16:25:17.000000000 +0000
@@ -235,7 +235,7 @@
     // on intercepted traffic which failed Host verification
     const HttpRequest *req = psstate->request;
     const bool isIntercepted = !req->flags.redirected &&
-                               (req->flags.intercepted || req->flags.spoofClientIp);
+                               (req->flags.intercepted || req->flags.interceptTproxy);
     const bool useOriginalDst = Config.onoff.client_dst_passthru || !req->flags.hostVerified;
     const bool choseDirect = fs && fs->code == HIER_DIRECT;
     if (isIntercepted && useOriginalDst && choseDirect) {
@@ -342,7 +342,7 @@
             if (psstate->paths->size() >= (unsigned int)Config.forward_max_tries)
                 break;
 
-            // for TPROXY we must skip unusable addresses.
+            // for TPROXY spoofing we must skip unusable addresses.
             if (psstate->request->flags.spoofClientIp && !(fs->_peer && fs->_peer->options.no_tproxy) ) {
                 if (ia->in_addrs[n].IsIPv4() != psstate->request->client_addr.IsIPv4()) {
                     // we CAN'T spoof the address on this link. find another.
diff -urN squid-3.3.1.vanilla/src/RequestFlags.h squid-3.3.1/src/RequestFlags.h
--- squid-3.3.1.vanilla/src/RequestFlags.h	2013-02-09 07:30:01.000000000 +0000
+++ squid-3.3.1/src/RequestFlags.h	2013-02-25 16:25:17.000000000 +0000
@@ -83,11 +83,16 @@
     bool accelerated :1;
     /** if set, ignore Cache-Control headers */
     bool ignoreCc :1;
+    ///
+    /// Set for requests handled by an "intercept" or "tproxy" port.
+    bool interceptTproxy :1;
     /** set for intercepted requests */
     bool intercepted :1;
     /** set if the Host: header passed verification */
     bool hostVerified :1;
-    /** request to spoof the client ip */
+    /// The client IP address should be spoofed when connecting to the web server.
+    /// This applies to TPROXY traffic that has not had spoofing disabled through
+    /// the spoof_client_ip squid.conf ACL.
     bool spoofClientIp :1;
     /** set if the request is internal (\see ClientHttpRequest::flags.internal)*/
     bool internal :1;
diff -urN squid-3.3.1.vanilla/src/SquidConfig.h squid-3.3.1/src/SquidConfig.h
--- squid-3.3.1.vanilla/src/SquidConfig.h	2013-02-09 07:30:01.000000000 +0000
+++ squid-3.3.1/src/SquidConfig.h	2013-02-25 16:25:17.000000000 +0000
@@ -398,6 +398,9 @@
 #if ICAP_CLIENT
         acl_access* icap;
 #endif
+        /// spoof_client_ip squid.conf acl.
+        /// nil unless configured
+        acl_access* spoof_client_ip;
     } accessList;
     AclDenyInfoList *denyInfoList;
 
diff -urN squid-3.3.1.vanilla/src/tools.cc squid-3.3.1/src/tools.cc
--- squid-3.3.1.vanilla/src/tools.cc	2013-02-09 07:30:01.000000000 +0000
+++ squid-3.3.1/src/tools.cc	2013-02-25 16:25:17.000000000 +0000
@@ -1169,7 +1169,7 @@
     AnyP::PortCfg *p = NULL;
     if ((p = Config.Sockaddr.http)) {
         // skip any special interception ports
-        while (p && (p->intercepted || p->spoof_client_ip))
+        while (p && (p->intercepted || p->interceptTproxy))
             p = p->next;
         if (p)
             return p->s.GetPort();
@@ -1178,7 +1178,7 @@
 #if USE_SSL
     if ((p = Config.Sockaddr.https)) {
         // skip any special interception ports
-        while (p && (p->intercepted || p->spoof_client_ip))
+        while (p && (p->intercepted || p->interceptTproxy))
             p = p->next;
         if (p)
             return p->s.GetPort();
diff -urN squid-3.3.1.vanilla/src/tunnel.cc squid-3.3.1/src/tunnel.cc
--- squid-3.3.1.vanilla/src/tunnel.cc	2013-02-09 07:30:01.000000000 +0000
+++ squid-3.3.1/src/tunnel.cc	2013-02-25 16:25:17.000000000 +0000
@@ -541,7 +541,7 @@
     TunnelStateData *tunnelState = (TunnelStateData *)data;
     debugs(26, 3, HERE << server << ", tunnelState=" << tunnelState);
 
-    if (tunnelState->request && (tunnelState->request->flags.spoofClientIp || tunnelState->request->flags.intercepted))
+    if (tunnelState->request && (tunnelState->request->flags.interceptTproxy || tunnelState->request->flags.intercepted))
         tunnelStartShoveling(tunnelState); // ssl-bumped connection, be quiet
     else {
         AsyncCall::Pointer call = commCbCall(5,5, "tunnelConnectedWriteDone",

Reply via email to