! This is a reduced part of the original follow-XFF patchset from ! devel.squid-cache.org for use with the FreeBSD squid-2.5 port. Index: src/acl.c --- src/acl.c 13 May 2003 02:14:12 -0000 1.43.2.16 +++ src/acl.c 23 Nov 2003 14:20:12 -0000 @@ -2001,6 +2001,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: src/cf.data.pre --- src/cf.data.pre 7 Nov 2003 03:14:30 -0000 1.49.2.46 +++ src/cf.data.pre 23 Nov 2003 14:20:17 -0000 @@ -2065,6 +2065,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: src/client_side.c --- src/client_side.c 2 Sep 2003 02:13:45 -0000 1.47.2.39 +++ src/client_side.c 23 Nov 2003 14:20:22 -0000 @@ -109,6 +109,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 clientCacheHit; @@ -177,10 +182,179 @@ 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; + if (Config.accessList.followXFF + && httpHeaderHas(&request->header, HDR_X_FORWARDED_FOR)) + { + 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); + } else { + /* not configured to follow X-Forwarded-For, or nothing to follow */ + debug(33, 5) ("clientFollowXForwardedForStart: nothing to do\n"); + clientFollowXForwardedForDone(-1, 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 */ + void clientAccessCheck(void *data) { clientHttpRequest *http = data; +#if FOLLOW_X_FORWARDED_FOR + if (! http->request->flags.done_follow_x_forwarded_for + && httpHeaderHas(&http->request->header, HDR_X_FORWARDED_FOR)) + { + /* + * There's an X-ForwardedFor header and we haven't yet tried + * to follow it to find the indirect_client_addr. Follow it now. + * clientFollowXForwardedForDone() will eventually pass control + * back to us. + * + * XXX perhaps our caller should have called + * clientFollowXForwardedForStart instead. Then we wouldn't + * need to do this little dance transferring control over + * there and then back here, and we wouldn't need the + * done_follow_x_forwarded_for flag. + */ + clientFollowXForwardedForStart(data); + return; + } +#endif /* FOLLOW_X_FORWARDED_FOR */ if (checkAccelOnly(http)) { /* deny proxy requests in accel_only mode */ debug(33, 1) ("clientAccessCheck: proxy request denied in accel_only mode\n"); @@ -325,6 +499,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->my_addr = old_request->my_addr; new_request->my_port = old_request->my_port; new_request->flags.redirected = 1; @@ -3051,6 +3228,9 @@ safe_free(http->log_uri); http->log_uri = xstrdup(urlCanonicalClean(request)); request->client_addr = conn->peer.sin_addr; +#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->http_ver = http->http_ver; Index: src/delay_pools.c --- src/delay_pools.c 19 Jun 2003 02:13:57 -0000 1.5.54.6 +++ src/delay_pools.c 23 Nov 2003 14:20:23 -0000 @@ -318,6 +318,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; *** src/structs.h.orig Sun Jun 26 12:45:58 2005 --- src/structs.h Sun Jun 26 12:48:45 2005 *************** *** 610,615 **** --- 610,620 ---- int accel_uses_host_header; int accel_no_pmtu_disc; int global_internal_static; + #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; acl *aclList; struct { *************** *** 631,636 **** --- 636,644 ---- 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; acl_deny_info_list *denyInfoList; struct _authConfig { *************** *** 1623,1628 **** --- 1631,1641 ---- unsigned int body_sent:1; unsigned int reset_tcp:1; unsigned int must_keepalive:1; + #if FOLLOW_X_FORWARDED_FOR + /* XXX this flag could be eliminated; + * see comments in clientAccessCheck */ + unsigned int done_follow_x_forwarded_for; + #endif /* FOLLOW_X_FORWARDED_FOR */ }; struct _link_list { *************** *** 1666,1671 **** --- 1679,1687 ---- int max_forwards; /* 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; HttpHeader header; *************** *** 1677,1682 **** --- 1693,1703 ---- const char *vary_headers; /* Used when varying entities are detected. Changes how the store key is calculated */ BODY_HANDLER *body_reader; void *body_reader_data; + #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 {