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;