This patch is generated from the follow_xff branch of HEAD in squid3
Tue Aug 17 18:59:36 2004 GMT
See http://devel.squid-cache.org/

Index: squid3/configure.in
diff -u squid3/configure.in:1.48 squid3/configure.in:1.11.2.6
--- squid3/configure.in:1.48	Sun Sep 14 19:12:57 2003
+++ squid3/configure.in	Tue Dec 30 14:54:59 2003
@@ -1368,6 +1368,24 @@
   fi
 ])
 
+follow_xff=1
+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"
+    follow_xff=1
+  fi
+])
+if test $follow_xff = 1; then
+    AC_DEFINE(FOLLOW_X_FORWARDED_FOR, 1, [Enable following X-Forwarded-For headers])
+else
+    AC_DEFINE(FOLLOW_X_FORWARDED_FOR, 0)
+fi
+
 AC_ARG_WITH(filedescriptors,
 [  --with-filedescriptors=NUMBER Force squid to support NUMBER filedescriptors],
 [ squid_filedescriptors_num=$withval ])
Index: squid3/src/ACLChecklist.cc
diff -u squid3/src/ACLChecklist.cc:1.15 squid3/src/ACLChecklist.cc:1.6.2.3
--- squid3/src/ACLChecklist.cc:1.15	Sun Sep 21 19:13:23 2003
+++ squid3/src/ACLChecklist.cc	Tue Dec 30 14:55:00 2003
@@ -488,7 +488,19 @@
 
     if (request != NULL) {
         checklist->request = requestLink(request);
-        checklist->src_addr = request->client_addr;
+#if FOLLOW_X_FORWARDED_FOR
+	/*
+	 * If configured to do so then use the indirect client address
+	 * instead of the direct client address.
+	 *
+	 * XXX: It would be better if if we had per-ACL granularity instead
+	 *      of the global acl_uses_indirect_client flag.
+	 */
+	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: squid3/src/DelayId.cc
diff -u squid3/src/DelayId.cc:1.13 squid3/src/DelayId.cc:1.4.2.4
--- squid3/src/DelayId.cc:1.13	Sat Sep 20 19:13:17 2003
+++ squid3/src/DelayId.cc	Tue Dec 30 14:55:00 2003
@@ -105,6 +105,11 @@
 
     for (pool = 0; pool < DelayPools::pools(); pool++) {
         ACLChecklist 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: squid3/src/HttpRequest.cc
diff -u squid3/src/HttpRequest.cc:1.14 squid3/src/HttpRequest.cc:1.2.8.4
--- squid3/src/HttpRequest.cc:1.14	Mon Sep  1 19:12:39 2003
+++ squid3/src/HttpRequest.cc	Tue Dec 30 14:55:00 2003
@@ -115,6 +115,10 @@
 
     req->client_addr = no_addr;
 
+#if FOLLOW_X_FORWARDED_FOR
+    req->indirect_client_addr = req->client_addr;
+#endif /* FOLLOW_X_FORWARDED_FOR */
+
     req->my_addr = no_addr;
 
     httpRequestHdrCacheInit(req);
Index: squid3/src/HttpRequest.h
diff -u squid3/src/HttpRequest.h:1.9 squid3/src/HttpRequest.h:1.1.12.3
--- squid3/src/HttpRequest.h:1.9	Thu Oct 16 19:12:32 2003
+++ squid3/src/HttpRequest.h	Tue Dec 30 14:55:00 2003
@@ -84,6 +84,9 @@
     /* these in_addr's could probably be sockaddr_in's */
 
     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;
Index: squid3/src/acl.cc
diff -u squid3/src/acl.cc:1.23 squid3/src/acl.cc:1.3.4.7
--- squid3/src/acl.cc:1.23	Mon Oct 20 19:12:44 2003
+++ squid3/src/acl.cc	Tue Dec 30 14:55:00 2003
@@ -476,7 +476,6 @@
     return true;
 }
 
-
 /*********************/
 /* Destroy functions */
 /*********************/
Index: squid3/src/cf.data.pre
diff -u squid3/src/cf.data.pre:1.45 squid3/src/cf.data.pre:1.6.2.6
--- squid3/src/cf.data.pre:1.45	Tue Oct 14 19:12:48 2003
+++ squid3/src/cf.data.pre	Tue Dec 30 14:55:00 2003
@@ -3949,6 +3949,92 @@
  broken_posts allow buggy_server
 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: mcast_miss_addr
 IFDEF: MULTICAST_MISS_STREAM
 TYPE: address
Index: squid3/src/client_side.cc
diff -u squid3/src/client_side.cc:1.58 squid3/src/client_side.cc:1.12.2.7
--- squid3/src/client_side.cc:1.58	Tue Dec 23 19:13:00 2003
+++ squid3/src/client_side.cc	Tue Dec 30 14:55:00 2003
@@ -2185,6 +2185,14 @@
     request->flags.internal = http->flags.internal;
     setLogUri (http, urlCanonicalClean(request));
     request->client_addr = conn->peer.sin_addr;
+#if FOLLOW_X_FORWARDED_FOR
+    /*
+     * indirect_client_addr starts off equal to client_addr, but
+     * X-Forwarded-For headers might cause indirect_client_addr to be
+     * changed later.
+     */
+    request->indirect_client_addr = request->client_addr;
+#endif /* FOLLOW_X_FORWARDED_FOR */
     request->client_port = ntohs(conn->peer.sin_port);
     request->my_addr = conn->me.sin_addr;
     request->my_port = ntohs(conn->me.sin_port);
@@ -2689,8 +2697,14 @@
 {
     ConnStateData *result = new ConnStateData;
     result->peer = *peer;
-    result->log_addr = peer->sin_addr;
-    result->log_addr.s_addr &= Config.Addrs.client_netmask.s_addr;
+#if FOLLOW_X_FORWARDED_FOR
+    /*
+     * log_addr starts off equal to the peer address (slightly
+     * anonymised with the client_netmask), but X-Forwarded-For headers
+     * might cause it to be changed later.
+     */
+#endif /* FOLLOW_X_FORWARDED_FOR */
+    result->setLogAddr(peer->sin_addr);
     result->me = *me;
     result->fd = fd;
     result->in.buf = (char *)memAllocBuf(CLIENT_REQ_BUF_SZ, &result->in.allocatedSize);
@@ -3120,6 +3134,16 @@
     openReference = this;
 }
 
+void
+ConnStateData::setLogAddr(struct in_addr newlogaddr)
+{
+    /*
+     * anonymise the log_addr by masking with the client_netmask.
+     */
+    log_addr = newlogaddr;
+    log_addr.s_addr &= Config.Addrs.client_netmask.s_addr;
+}
+
 bool
 ConnStateData::transparent() const
 {
Index: squid3/src/client_side.h
diff -u squid3/src/client_side.h:1.7 squid3/src/client_side.h:1.7.2.1
--- squid3/src/client_side.h:1.7	Thu Aug 14 19:12:38 2003
+++ squid3/src/client_side.h	Fri Aug 29 10:10:13 2003
@@ -183,6 +183,7 @@
     struct sockaddr_in me;
 
     struct in_addr log_addr;
+    void setLogAddr(struct in_addr newlogaddr);
     char rfc931[USER_IDENT_SZ];
     int nrequests;
 
Index: squid3/src/client_side_request.cc
diff -u squid3/src/client_side_request.cc:1.26 squid3/src/client_side_request.cc:1.4.6.11
--- squid3/src/client_side_request.cc:1.26	Sat Sep  6 19:12:33 2003
+++ squid3/src/client_side_request.cc	Tue Dec 30 14:55:01 2003
@@ -76,6 +76,11 @@
     ACLChecklist *acl_checklist;	/* need ptr back so we can unreg if needed */
     int redirect_state;
     clientHttpRequest *http;
+#if FOLLOW_X_FORWARDED_FOR
+    /* XXX: a list of IP addresses would be a better data structure
+     *      than this String */
+    String xff_state; /* unprocessed part of X-Forwarded-For header */
+#endif /* FOLLOW_X_FORWARDED_FOR */
 
 private:
     CBDATA_CLASS(ClientRequestContext);
@@ -108,6 +113,12 @@
 
 /* Local functions */
 /* other */
+#if FOLLOW_X_FORWARDED_FOR
+static void clientFollowXForwardedForStart(ClientRequestContext * context);
+static void clientFollowXForwardedForNext(ClientRequestContext * context);
+static void clientFollowXForwardedForDone(int answer, void *data);
+#endif /* FOLLOW_X_FORWARDED_FOR */
+static void clientAccessCheckContinue(ClientRequestContext * context);
 static void clientAccessCheckDone(int, void *);
 static int clientCachable(clientHttpRequest * http);
 static int clientHierarchical(clientHttpRequest * http);
@@ -339,6 +350,10 @@
 
     request->client_addr = no_addr;
 
+#if FOLLOW_X_FORWARDED_FOR
+    request->indirect_client_addr = request->client_addr;
+#endif /* FOLLOW_X_FORWARDED_FOR */
+
     request->my_addr = no_addr;	/* undefined for internal requests */
 
     request->my_port = 0;
@@ -353,11 +368,198 @@
     return 0;
 }
 
-/* This is the entry point for external users of the client_side routines */
+#if FOLLOW_X_FORWARDED_FOR
+/*
+ * The clientFollowXForwardedForStart/Next/Done() functions gain control
+ * between clientAccessCheck() and clientAccessCheckContinue().
+ *
+ * clientFollowXForwardedForStart() copies the X-Forwarded-For
+ * header into context->xff_state 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 context->xff_state and loops back to
+ * clientFollowXForwardedForNext(), or cleans up and passes control to
+ * clientAccessCheckContinue().
+ */
+
+static void
+clientFollowXForwardedForStart(ClientRequestContext * context)
+{
+    clientHttpRequest *http = context->http;
+    HttpRequest *request = http->request;
+    if (Config.accessList.followXFF
+	&& httpHeaderHas(&request->header, HDR_X_FORWARDED_FOR))
+    {
+	context->xff_state = httpHeaderGetList(
+			&request->header, HDR_X_FORWARDED_FOR);
+	debug(85, 5) ("clientFollowXForwardedForStart: "
+		      "indirect_client_addr=%s XFF='%s'\n",
+		      inet_ntoa(request->indirect_client_addr),
+		      context->xff_state.buf());
+	clientFollowXForwardedForNext(context);
+    } else {
+	/* not configured to follow X-Forwarded-For, or nothing to follow */
+	debug(85, 5) ("clientFollowXForwardedForStart: nothing to do\n");
+	clientFollowXForwardedForDone(ACCESS_DENIED, context);
+    }
+}
+
+static void
+clientFollowXForwardedForNext(ClientRequestContext * context)
+{
+    clientHttpRequest *http = context->http;
+    HttpRequest *request = http->request;
+    debug(85, 5) ("clientFollowXForwardedForNext: "
+		  "indirect_client_addr=%s XFF='%s'\n",
+		  inet_ntoa(request->indirect_client_addr),
+		  context->xff_state.buf());
+    if (context->xff_state.size() != 0) {
+	/* check the acl to see whether to believe the X-Forwarded-For header */
+	context->acl_checklist = clientAclChecklistCreate(
+			Config.accessList.followXFF, http);
+	context->acl_checklist->nonBlockingCheck(clientFollowXForwardedForDone,
+			context);
+    } else {
+	/* nothing left to follow */
+	debug(85, 5) ("clientFollowXForwardedForNext: nothing more to do\n");
+	clientFollowXForwardedForDone(ACCESS_DENIED, context);
+    }
+}
+
+static void
+clientFollowXForwardedForDone(int answer, void *data)
+{
+    ClientRequestContext *context = (ClientRequestContext *)data;
+    clientHttpRequest *http = context->http;
+    HttpRequest *request = http->request;
+    context->acl_checklist = NULL;
+
+    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 context->xff_state 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(85, 5) ("clientFollowXForwardedForDone: "
+		      "indirect_client_addr=%s is trusted\n",
+		      inet_ntoa(request->indirect_client_addr));
+	p = context->xff_state.buf();
+	l = context->xff_state.size();
+
+	/*
+	 * XXX: context->xff_state 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--;
+	context->xff_state.cut(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(85, 3) ("clientFollowXForwardedForDone: "
+			  "malformed address '%s'\n",
+			  asciiaddr);
+	    goto cleanup;
+	}
+	debug(85, 3) ("clientFollowXForwardedForDone: "
+		      "changing indirect_client_addr from %s to '%s'\n",
+		      inet_ntoa(request->indirect_client_addr),
+		      asciiaddr);
+	request->indirect_client_addr = addr;
+	context->xff_state.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.
+	     */
+	    goto cleanup;
+	}
+	clientFollowXForwardedForNext(context);
+	return;
+    } else {
+	/*
+	 * (answer == ACCESS_DENIED) can happen when an ACL says
+	 * we have to stop examining the X-Forwarded-For header,
+	 * or when we have finished examining the header.
+	 */
+	debug(85, 5) ("clientFollowXForwardedForDone: "
+		      "indirect_client_addr=%s %s\n",
+		      inet_ntoa(request->indirect_client_addr),
+		      (context->xff_state.size() == 0
+			  ? "nothing more to do"
+			  : "not trusted")
+		      );
+    }
+cleanup:
+    /* clean up, and pass control to clientAccessCheck */
+    debug(85, 6) ("clientFollowXForwardedForDone: cleanup\n");
+    if (Config.onoff.log_uses_indirect_client && http->getConn().getRaw()) {
+	/*
+         * Ensure that the access log shows the indirect client
+         * instead of the direct client.
+	 */
+	http->getConn()->setLogAddr(request->indirect_client_addr);
+	debug(85, 3) ("clientFollowXForwardedForDone: "
+		      "set log_addr=%s\n",
+		      inet_ntoa(http->getConn()->log_addr));
+    }
+    context->xff_state.clean();
+    clientAccessCheckContinue(context);
+}
+#endif /* FOLLOW_X_FORWARDED_FOR */
+
+/*
+ * clientAccessCheck() is the entry point for external users of the
+ * client_side routines.
+ */
+/*
+ * If FOLLOW_X_FORWARDED_FOR is enabled, then control passes
+ * through clientFollowXForwardedForStart/Next/Done() in
+ * between clientAccessCheck() and clientAccessCheckContinue().
+ * Oterwise, clientAccessCheck() passes control directly to
+ * clientAccessCheckContinue().
+ *
+ * XXX: This seems a little clumsy.  We should probably rename
+ *      clientAccessCheck() to something that suggests its entry
+ *      point qualities, and rename clientAccessCheckContinue() to
+ *      clientAccessCheck().
+ */
 void
 clientAccessCheck(ClientHttpRequest *http)
 {
     ClientRequestContext *context = new ClientRequestContext(http);
+
+#if FOLLOW_X_FORWARDED_FOR
+    clientFollowXForwardedForStart(context);
+#else /* ! FOLLOW_X_FORWARDED_FOR */
+    clientAccessCheckContinue(context);
+#endif /* FOLLOW_X_FORWARDED_FOR */
+}
+
+static void
+clientAccessCheckContinue(ClientRequestContext * context)
+{
+    clientHttpRequest *http = context->http;
     context->acl_checklist =
         clientAclChecklistCreate(Config.accessList.http, http);
     context->acl_checklist->nonBlockingCheck(clientAccessCheckDone, context);
@@ -746,6 +948,9 @@
         new_request->http_ver = old_request->http_ver;
         httpHeaderAppend(&new_request->header, &old_request->header);
         new_request->client_addr = old_request->client_addr;
+#if FOLLOW_X_FORWARDED_FOR
+	new_request->indirect_client_addr = old_request->indirect_client_addr;
+#endif /* FOLLOW_X_FORWARDED_FOR */
         new_request->client_port = old_request->client_port;
         new_request->my_addr = old_request->my_addr;
         new_request->my_port = old_request->my_port;
@@ -851,7 +1056,7 @@
 ClientHttpRequest::httpStart()
 {
     logType = LOG_TAG_NONE;
-    debug(85, 4) ("ClientHttpRequest::httpStart: %s for '%s'\n",
+    debug(33, 4) ("ClientHttpRequest::httpStart: %s for '%s'\n",
                   log_tags[logType], uri);
     /* no one should have touched this */
     assert(out.offset == 0);
Index: squid3/src/redirect.cc
diff -u squid3/src/redirect.cc:1.11 squid3/src/redirect.cc:1.1.12.5
--- squid3/src/redirect.cc:1.11	Thu Nov  6 19:12:39 2003
+++ squid3/src/redirect.cc	Tue Dec 30 14:55:01 2003
@@ -150,6 +150,9 @@
 
     r = cbdataAlloc(redirectStateData);
     r->orig_url = xstrdup(http->uri);
+    /* XXX: Why does this use conn->log_addr (which might be
+     * masked with the client_netmask) instead of request->client_addr
+     * or request->indirect_client_addr? */
     r->client_addr = conn.getRaw() != NULL ? conn->log_addr : no_addr;
 
     if (http->request->auth_user_request)
Index: squid3/src/structs.h
diff -u squid3/src/structs.h:1.50 squid3/src/structs.h:1.9.2.8
--- squid3/src/structs.h:1.50	Mon Oct 20 19:12:45 2003
+++ squid3/src/structs.h	Tue Dec 30 14:55:02 2003
@@ -117,6 +117,16 @@
     acl_address *next;
     acl_list *aclList;
 
+#if FOLLOW_X_FORWARDED_FOR
+    /*
+     * Clients of the ACL-related functions typically set the
+     * addr field to the direct client address.  If the
+     * acl_uses_indirect_client option is set, then ACL-related
+     * functions may replace the addr field with the indirect client
+     * address.
+     */
+#endif /* FOLLOW_X_FORWARDED_FOR */
+
     struct in_addr addr;
 };
 
@@ -560,6 +570,11 @@
         int check_hostnames;
         int via;
         int emailErrData;
+#if FOLLOW_X_FORWARDED_FOR
+	int acl_uses_indirect_client;
+	int delay_pool_uses_indirect_client;
+	int log_uses_indirect_client;
+#endif /* FOLLOW_X_FORWARDED_FOR */
     }
 
     onoff;
@@ -591,6 +606,9 @@
         acl_access *reply;
         acl_address *outgoing_address;
         acl_tos *outgoing_tos;
+#if FOLLOW_X_FORWARDED_FOR
+	acl_access *followXFF;
+#endif /* FOLLOW_X_FORWARDED_FOR */
     }
 
     accessList;
