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 {