This patch is generated from the follow_xff-2_6 branch of HEAD in squid
Fri Aug 18 22:56:46 2006 GMT
See http://devel.squid-cache.org/

Index: squid/configure.in
diff -u squid/configure.in:1.118 squid/configure.in:1.112.2.3
--- squid/configure.in:1.118	Sat Jun  3 18:51:42 2006
+++ squid/configure.in	Sat Jun  3 19:18:25 2006
@@ -1396,6 +1396,18 @@
   fi
 ])
 
+AC_ARG_ENABLE(follow-x-forwarded-for,
+[  --enable-follow-x-forwarded-for
+                          Enable support for following the X-Forwarded-For
+                          HTTP header to try to find the IP address of the
+                          original or indirect client when a request has
+                          been forwarded through other proxies.],
+[ if test "$enableval" = "yes" ; then
+    echo "follow X-Forwarded-For enabled"
+    AC_DEFINE(FOLLOW_X_FORWARDED_FOR, 1, [Enable following X-Forwarded-For headers])
+  fi
+])
+
 # Force some compilers to use ANSI features
 #
 case "$host" in
Index: squid/src/acl.c
diff -u squid/src/acl.c:1.72 squid/src/acl.c:1.72.2.1
--- squid/src/acl.c:1.72	Tue May 30 05:50:38 2006
+++ squid/src/acl.c	Tue May 30 14:53:57 2006
@@ -2372,6 +2372,11 @@
     cbdataLock(A);
     if (request != NULL) {
 	checklist->request = requestLink(request);
+#if FOLLOW_X_FORWARDED_FOR
+	if (Config.onoff.acl_uses_indirect_client) {
+	    checklist->src_addr = request->indirect_client_addr;
+	} else
+#endif /* FOLLOW_X_FORWARDED_FOR */
 	checklist->src_addr = request->client_addr;
 	checklist->my_addr = request->my_addr;
 	checklist->my_port = request->my_port;
Index: squid/src/cf.data.pre
diff -u squid/src/cf.data.pre:1.121 squid/src/cf.data.pre:1.118.2.2
--- squid/src/cf.data.pre:1.121	Fri Jun  2 05:54:38 2006
+++ squid/src/cf.data.pre	Sat Jun  3 18:01:29 2006
@@ -2723,6 +2723,92 @@
 NOCOMMENT_END
 DOC_END
 
+NAME: follow_x_forwarded_for
+TYPE: acl_access
+IFDEF: FOLLOW_X_FORWARDED_FOR
+LOC: Config.accessList.followXFF
+DEFAULT: none
+DEFAULT_IF_NONE: deny all
+DOC_START
+	Allowing or Denying the X-Forwarded-For header to be followed to
+	find the original source of a request.
+
+	Requests may pass through a chain of several other proxies
+	before reaching us.  The X-Forwarded-For header will contain a
+	comma-separated list of the IP addresses in the chain, with the
+	rightmost address being the most recent.
+
+	If a request reaches us from a source that is allowed by this
+	configuration item, then we consult the X-Forwarded-For header
+	to see where that host received the request from.  If the
+	X-Forwarded-For header contains multiple addresses, and if
+	acl_uses_indirect_client is on, then we continue backtracking
+	until we reach an address for which we are not allowed to
+	follow the X-Forwarded-For header, or until we reach the first
+	address in the list.  (If acl_uses_indirect_client is off, then
+	it's impossible to backtrack through more than one level of
+	X-Forwarded-For addresses.)
+
+	The end result of this process is an IP address that we will
+	refer to as the indirect client address.  This address may
+	be treated as the client address for access control, delay
+	pools and logging, depending on the acl_uses_indirect_client,
+	delay_pool_uses_indirect_client and log_uses_indirect_client
+	options.
+
+	SECURITY CONSIDERATIONS:
+
+		Any host for which we follow the X-Forwarded-For header
+		can place incorrect information in the header, and Squid
+		will use the incorrect information as if it were the
+		source address of the request.  This may enable remote
+		hosts to bypass any access control restrictions that are
+		based on the client's source addresses.
+
+	For example:
+
+		acl localhost src 127.0.0.1
+		acl my_other_proxy srcdomain .proxy.example.com
+		follow_x_forwarded_for allow localhost
+		follow_x_forwarded_for allow my_other_proxy
+DOC_END
+
+NAME: acl_uses_indirect_client
+COMMENT: on|off
+TYPE: onoff
+IFDEF: FOLLOW_X_FORWARDED_FOR
+DEFAULT: on
+LOC: Config.onoff.acl_uses_indirect_client
+DOC_START
+	Controls whether the indirect client address
+	(see follow_x_forwarded_for) is used instead of the
+	direct client address in acl matching.
+DOC_END
+
+NAME: delay_pool_uses_indirect_client
+COMMENT: on|off
+TYPE: onoff
+IFDEF: FOLLOW_X_FORWARDED_FOR && DELAY_POOLS
+DEFAULT: on
+LOC: Config.onoff.delay_pool_uses_indirect_client
+DOC_START
+	Controls whether the indirect client address
+	(see follow_x_forwarded_for) is used instead of the
+	direct client address in delay pools.
+DOC_END
+
+NAME: log_uses_indirect_client
+COMMENT: on|off
+TYPE: onoff
+IFDEF: FOLLOW_X_FORWARDED_FOR
+DEFAULT: on
+LOC: Config.onoff.log_uses_indirect_client
+DOC_START
+	Controls whether the indirect client address
+	(see follow_x_forwarded_for) is used instead of the
+	direct client address in the access log.
+DOC_END
+
 NAME: http_access
 TYPE: acl_access
 LOC: Config.accessList.http
Index: squid/src/client_side.c
diff -u squid/src/client_side.c:1.111 squid/src/client_side.c:1.107.2.3
--- squid/src/client_side.c:1.111	Sat Jun  3 17:50:50 2006
+++ squid/src/client_side.c	Sat Jun  3 18:01:30 2006
@@ -128,6 +128,11 @@
 #if USE_IDENT
 static IDCB clientIdentDone;
 #endif
+#if FOLLOW_X_FORWARDED_FOR
+static void clientFollowXForwardedForStart(void *data);
+static void clientFollowXForwardedForNext(void *data);
+static void clientFollowXForwardedForDone(int answer, void *data);
+#endif /* FOLLOW_X_FORWARDED_FOR */
 static int clientOnlyIfCached(clientHttpRequest * http);
 static STCB clientSendMoreData;
 static STCB clientSendMoreHeaderData;
@@ -191,6 +196,160 @@
     return ch;
 }
 
+#if FOLLOW_X_FORWARDED_FOR
+/*
+ * clientFollowXForwardedForStart() copies the X-Forwarded-For
+ * header into x_forwarded_for_iterator and passes control to
+ * clientFollowXForwardedForNext().
+ *
+ * clientFollowXForwardedForNext() checks the indirect_client_addr
+ * against the followXFF ACL and passes the result to
+ * clientFollowXForwardedForDone().
+ *
+ * clientFollowXForwardedForDone() either grabs the next address
+ * from the tail of x_forwarded_for_iterator and loops back to
+ * clientFollowXForwardedForNext(), or cleans up and passes control to
+ * clientAccessCheck().
+ */
+
+static void
+clientFollowXForwardedForStart(void *data)
+{
+    clientHttpRequest *http = data;
+    request_t *request = http->request;
+    request->x_forwarded_for_iterator = httpHeaderGetList(
+			&request->header, HDR_X_FORWARDED_FOR);
+    debug(33, 5) ("clientFollowXForwardedForStart: indirect_client_addr=%s XFF='%s'\n",
+		    inet_ntoa(request->indirect_client_addr),
+		    strBuf(request->x_forwarded_for_iterator));
+    clientFollowXForwardedForNext(http);
+}
+
+static void
+clientFollowXForwardedForNext(void *data)
+{
+    clientHttpRequest *http = data;
+    request_t *request = http->request;
+    debug(33, 5) ("clientFollowXForwardedForNext: indirect_client_addr=%s XFF='%s'\n",
+		    inet_ntoa(request->indirect_client_addr),
+		    strBuf(request->x_forwarded_for_iterator));
+    if (strLen(request->x_forwarded_for_iterator) != 0) {
+	/* check the acl to see whether to believe the X-Forwarded-For header */
+	http->acl_checklist = clientAclChecklistCreate(
+			Config.accessList.followXFF, http);
+	aclNBCheck(http->acl_checklist, clientFollowXForwardedForDone, http);
+    } else {
+	/* nothing left to follow */
+	debug(33, 5) ("clientFollowXForwardedForNext: nothing more to do\n");
+	clientFollowXForwardedForDone(-1, http);
+    }
+}
+
+static void
+clientFollowXForwardedForDone(int answer, void *data)
+{
+    clientHttpRequest *http = data;
+    request_t *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
+     * there's nothing left to do.
+     */
+    if (answer == ACCESS_ALLOWED) {
+	/*
+	 * 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.
+	 */
+	const char *p;
+	const char *asciiaddr;
+	int l;
+	struct in_addr addr;
+	debug(33, 5) ("clientFollowXForwardedForDone: indirect_client_addr=%s is trusted\n",
+			inet_ntoa(request->indirect_client_addr));
+	p = strBuf(request->x_forwarded_for_iterator);
+	l = strLen(request->x_forwarded_for_iterator);
+
+	/*
+	 * XXX x_forwarded_for_iterator should really be a list of
+	 * IP addresses, but it's a String instead.  We have to
+	 * walk backwards through the String, biting off the last
+	 * comma-delimited part each time.  As long as the data is in
+	 * a String, we should probably implement and use a variant of
+	 * strListGetItem() that walks backwards instead of forwards
+	 * through a comma-separated list.  But we don't even do that;
+	 * we just do the work in-line here.
+	 */
+	/* skip trailing space and commas */
+	while (l > 0 && (p[l-1] == ',' || xisspace(p[l-1])))
+	    l--;
+	strCut(request->x_forwarded_for_iterator, l);
+	/* look for start of last item in list */
+	while (l > 0 && ! (p[l-1] == ',' || xisspace(p[l-1])))
+	    l--;
+	asciiaddr = p+l;
+	if (inet_aton(asciiaddr, &addr) == 0) {
+	    /* the address is not well formed; do not use it */
+	    debug(33, 3) ("clientFollowXForwardedForDone: malformed address '%s'\n",
+			    asciiaddr);
+	    goto done;
+	}
+	debug(33, 3) ("clientFollowXForwardedForDone: changing indirect_client_addr from %s to '%s'\n",
+		    inet_ntoa(request->indirect_client_addr),
+		    asciiaddr);
+	request->indirect_client_addr = addr;
+	strCut(request->x_forwarded_for_iterator, 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.
+	     */
+	    goto done;
+	}
+	clientFollowXForwardedForNext(http);
+	return;
+    } else if (answer == ACCESS_DENIED) {
+	debug(33, 5) ("clientFollowXForwardedForDone: indirect_client_addr=%s not trusted\n",
+			inet_ntoa(request->indirect_client_addr));
+    } else {
+	debug(33, 5) ("clientFollowXForwardedForDone: indirect_client_addr=%s nothing more to do\n",
+			inet_ntoa(request->indirect_client_addr));
+    }
+done:
+    /* clean up, and pass control to clientAccessCheck */
+    debug(33, 6) ("clientFollowXForwardedForDone: cleanup\n");
+    if (Config.onoff.log_uses_indirect_client) {
+	/*
+	 * Ensure that the access log shows the indirect client
+	 * instead of the direct client.
+	 */
+	ConnStateData *conn = http->conn;
+	conn->log_addr = request->indirect_client_addr;
+	conn->log_addr.s_addr &= Config.Addrs.client_netmask.s_addr;
+	debug(33, 3) ("clientFollowXForwardedForDone: setting log_addr=%s\n",
+			inet_ntoa(conn->log_addr));
+    }
+    stringClean(&request->x_forwarded_for_iterator);
+    request->flags.done_follow_x_forwarded_for = 1;
+    http->acl_checklist = NULL; /* XXX do we need to aclChecklistFree() ? */
+    clientAccessCheck(http);
+}
+#endif /* FOLLOW_X_FORWARDED_FOR */
+
+static void
+clientCheckFollowXForwardedFor(void *data)
+{
+    clientHttpRequest *http = data;
+#if FOLLOW_X_FORWARDED_FOR
+    if (Config.accessList.followXFF && httpHeaderHas(&http->request->header, HDR_X_FORWARDED_FOR)) {
+	clientFollowXForwardedForStart(data);
+	return;
+    }
+#endif
+    clientAccessCheck(data);
+}
+
 static void
 clientAccessCheck(void *data)
 {
@@ -448,6 +607,9 @@
 	httpHeaderAppend(&new_request->header, &old_request->header);
 	new_request->client_addr = old_request->client_addr;
 	new_request->client_port = old_request->client_port;
+#if FOLLOW_X_FORWARDED_FOR
+	new_request->indirect_client_addr = old_request->indirect_client_addr;
+#endif /* FOLLOW_X_FORWARDED_FOR */
 	new_request->my_addr = old_request->my_addr;
 	new_request->my_port = old_request->my_port;
 	new_request->client_port = old_request->client_port;
@@ -2855,7 +3017,7 @@
 	 */
 	/* if it was a pipelined CONNECT kick it alive here */
 	if (http->request->method == METHOD_CONNECT)
-	    clientAccessCheck(http);
+	    clientCheckFollowXForwardedFor(http);
     } else {
 	debug(33, 2) ("clientKeepaliveNextRequest: FD %d Sending next\n",
 	    conn->fd);
@@ -3744,6 +3906,9 @@
 	    request->flags.internal = http->flags.internal;
 	    request->client_addr = conn->peer.sin_addr;
 	    request->client_port = conn->peer.sin_port;
+#if FOLLOW_X_FORWARDED_FOR
+	    request->indirect_client_addr = request->client_addr;
+#endif /* FOLLOW_X_FORWARDED_FOR */
 	    request->my_addr = conn->me.sin_addr;
 	    request->my_port = ntohs(conn->me.sin_port);
 	    request->client_port = ntohs(conn->peer.sin_port);
@@ -3794,7 +3959,7 @@
 		/* Stop reading requests... */
 		commSetSelect(fd, COMM_SELECT_READ, NULL, NULL, 0);
 		if (conn->chr == http)
-		    clientAccessCheck(http);
+		    clientCheckFollowXForwardedFor(http);
 		else {
 		    debug(33, 1) ("WARNING: pipelined CONNECT request seen from %s\n", inet_ntoa(http->conn->peer.sin_addr));
 		    debugObj(33, 1, "Previous request:\n", conn->chr->request, (ObjPackMethod) & httpRequestPackDebug);
@@ -3802,7 +3967,7 @@
 		}
 		break;
 	    } else {
-		clientAccessCheck(http);
+		clientCheckFollowXForwardedFor(http);
 	    }
 	} else if (parser_return_code == 0) {
 	    /*
Index: squid/src/delay_pools.c
diff -u squid/src/delay_pools.c:1.11 squid/src/delay_pools.c:1.11.22.1
--- squid/src/delay_pools.c:1.11	Fri Apr 28 04:10:52 2006
+++ squid/src/delay_pools.c	Tue May 30 14:53:58 2006
@@ -319,6 +319,11 @@
     r = http->request;
 
     memset(&ch, '\0', sizeof(ch));
+#if FOLLOW_X_FORWARDED_FOR
+    if (Config.onoff.delay_pool_uses_indirect_client) {
+	ch.src_addr = r->indirect_client_addr;
+    } else
+#endif /* FOLLOW_X_FORWARDED_FOR */
     ch.src_addr = r->client_addr;
     ch.my_addr = r->my_addr;
     ch.my_port = r->my_port;
Index: squid/src/structs.h
diff -u squid/src/structs.h:1.102 squid/src/structs.h:1.100.2.2
--- squid/src/structs.h:1.102	Thu Jun  1 17:14:54 2006
+++ squid/src/structs.h	Sat Jun  3 18:01:32 2006
@@ -668,6 +668,11 @@
 	int check_hostnames;
 	int allow_underscore;
 	int cache_vary;
+#if FOLLOW_X_FORWARDED_FOR
+	int acl_uses_indirect_client;
+	int delay_pool_uses_indirect_client;
+	int log_uses_indirect_client;
+#endif
     } onoff;
 #if LINUX_TPROXY
     u_short tproxy_port;
@@ -699,6 +704,9 @@
 	acl_access *htcp;
 	acl_access *htcp_clr;
 #endif
+#if FOLLOW_X_FORWARDED_FOR
+	acl_access *followXFF;
+#endif
     } accessList;
     acl_deny_info_list *denyInfoList;
     struct _authConfig {
@@ -1774,6 +1782,11 @@
     unsigned int reset_tcp:1;
     unsigned int must_keepalive:1;
     unsigned int pinned:1;	/* If set, this request is tightly tied to a client-side connection */
+#if FOLLOW_X_FORWARDED_FOR
+    /* XXX this flag could be eliminated;
+     * see comments in clientAccessCheck */
+    unsigned int done_follow_x_forwarded_for;
+#endif
 };
 
 struct _link_list {
@@ -1822,6 +1835,9 @@
     /* these in_addr's could probably be sockaddr_in's */
     in_port_t client_port;
     struct in_addr client_addr;
+#if FOLLOW_X_FORWARDED_FOR
+    struct in_addr indirect_client_addr; /* after following X-Forwarded-For */
+#endif /* FOLLOW_X_FORWARDED_FOR */
     struct in_addr my_addr;
     unsigned short my_port;
     HttpHeader header;
@@ -1844,6 +1860,11 @@
     String extacl_log;		/* String to be used for access.log purposes */
     const char *extacl_user;	/* User name returned by extacl lookup */
     const char *extacl_passwd;	/* Password returned by extacl lookup */
+#if FOLLOW_X_FORWARDED_FOR
+    /* XXX a list of IP addresses would be a better data structure
+     * than this String */
+    String x_forwarded_for_iterator;
+#endif /* FOLLOW_X_FORWARDED_FOR */
 };
 
 struct _cachemgr_passwd {