/* ==================================================================== * Copyright (c) 2000 The Apache Group. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. All advertising materials mentioning features or use of this * software must display the following acknowledgment: * "This product includes software developed by the Apache Group * for use in the Apache HTTP server project (http://www.apache.org/)." * * 4. The names "Apache Server" and "Apache Group" must not be used to * endorse or promote products derived from this software without * prior written permission. For written permission, please contact * apache@apache.org. * * 5. Products derived from this software may not be called "Apache" * nor may "Apache" appear in their names without prior written * permission of the Apache Group. * * 6. Redistributions of any form whatsoever must retain the following * acknowledgment: * "This product includes software developed by the Apache Group * for use in the Apache HTTP server project (http://www.apache.org/)." * * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE GROUP OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Group and was originally based * on public domain software written at the National Center for * Supercomputing Applications, University of Illinois, Urbana-Champaign. * For more information on the Apache Group and the Apache HTTP server * project, please see . * */ /** $Id: mod_extract_forwarded.c,v 1.8 2003/03/18 21:27:31 ahosey Exp $ **/ /** Release version: 1.4 (nevermind the CVS version up there) **/ /** 1.4 adds the code from Richard Barrett * which resets the conn_rec after each request, and applies a * somewhat more rigorous (or lenient, depending on how you look at * it) interpretation of the X-Forwarded-For proxy chain, and does the * conditional checking of PROXY_ADDR. 1.4 also removes support for * Apache 1.2 - isn't it time you upgraded? **/ #include #include "httpd.h" #include "http_config.h" #include "http_core.h" #include "http_main.h" #include "http_log.h" #include "http_protocol.h" #include "scoreboard.h" module extract_forwarded_module; typedef struct { int allow_cache; table *allowed_proxies; table *denied_proxies; } fwd_dir_conf; /* Given an IP as "arg", make sure it is in the allowed_proxies * table. It will only ever exist once in a given table. */ static const char *add_forwarder(cmd_parms *cmd, fwd_dir_conf *conf, const char *arg) { struct hostent *hptr = NULL; char** haddr; /* "all" keyword replaces everything with just itself. */ if (strcasecmp(arg, "all") == 0) { ap_clear_table(conf->allowed_proxies); ap_table_set(conf->allowed_proxies, arg, "t"); } else { hptr = gethostbyname(arg); if (hptr) { for (haddr=hptr->h_addr_list; *haddr; haddr++) ap_table_set(conf->allowed_proxies, inet_ntoa(*((struct in_addr*)(*haddr))), "t"); } } return NULL; } /* Given an IP as "arg", make sure it is NOT in the allowed_proxies table. */ static const char *rm_forwarder(cmd_parms *cmd, fwd_dir_conf *conf, const char *arg) { struct hostent *hptr = NULL; char** haddr; if (strcasecmp(arg, "all") == 0) { ap_clear_table(conf->denied_proxies); ap_table_set(conf->denied_proxies, arg, "t"); } else { hptr = gethostbyname(arg); if (hptr) { for (haddr=hptr->h_addr_list; *haddr; haddr++) ap_table_set(conf->denied_proxies, inet_ntoa(*((struct in_addr*)(*haddr))), "t"); } } return NULL; } /* Set allow_cache in this fwd_dir_conf to be whatever FLAG we are given. */ static const char *toggle_caching(cmd_parms *cmd, void *mconfig, int flag) { fwd_dir_conf *conf = (fwd_dir_conf*)mconfig; conf->allow_cache = flag; return NULL; } /* Hook up the cmd functions we have lovingly defined above. */ command_rec extract_cmds[] = { { "AddAcceptForwarder", add_forwarder, NULL, OR_OPTIONS, ITERATE, "One or more proxy IPs to add to the accept list" }, { "RemoveAcceptForwarder", rm_forwarder, NULL, OR_OPTIONS, ITERATE, "One or more proxy IPs to remove from the accept list" }, { "AllowForwarderCaching", toggle_caching, NULL, OR_OPTIONS, FLAG, "Allow caching of this page if fetched by proxy - On or Off" }, { NULL } }; /* This is a callback function used by merge_fwd_dir_conf(). See that * function for more details. */ static int take_out_proxies(void *allows, const char *key, const char* val) { /* If key exists in the allows list take it out. */ ap_table_unset((table*)allows, key); return 1; } /* The next two functions are the fwd_dir_conf creator and merger. */ static void *create_fwd_dir_conf(pool *p, char *dir) { fwd_dir_conf *conf = (fwd_dir_conf*)ap_pcalloc(p, sizeof(fwd_dir_conf)); /* This defaults to a 2, meaning "unspecified" */ conf->allow_cache = 2; /* We start with an empty table, which means ignore all * Forwarded-For headers. */ conf->allowed_proxies = ap_make_table(p, 0); conf->denied_proxies = ap_make_table(p, 0); return (void*)conf; } static void *merge_fwd_dir_conf(pool *p, void *base_conf, void* new_conf) { fwd_dir_conf *parent = (fwd_dir_conf*)base_conf; fwd_dir_conf *child = (fwd_dir_conf*)new_conf; fwd_dir_conf *merged = (fwd_dir_conf*)ap_pcalloc(p, sizeof(fwd_dir_conf)); int altered_child_denied = 0; int altered_child_allowed = 0; /* If child->allow_cache was explicitly set in this dir, use * that. Else use the parent value. If the parent value is unset, * set to "On" */ if (child->allow_cache != 2) merged->allow_cache = child->allow_cache; else if (parent->allow_cache == 2) merged->allow_cache = 1; else merged->allow_cache = parent->allow_cache; /* The new allowed list starts as the parent list. */ merged->allowed_proxies = ap_copy_table(p, parent->allowed_proxies); merged->denied_proxies = ap_copy_table(p, parent->denied_proxies); /* Process "all"s before anything else, and process Removes before * Adds. */ if (ap_table_get(child->denied_proxies, "all")) { ap_clear_table(merged->allowed_proxies); /* Flag to remind us we need to undo this change. */ altered_child_denied = 1; ap_table_unset(child->denied_proxies, "all"); } if (ap_table_get(child->allowed_proxies, "all")) { ap_clear_table(merged->allowed_proxies); ap_table_set(merged->allowed_proxies, "all", "t"); /* Flag to remind us we need to undo this change. */ altered_child_allowed = 1; ap_table_unset(child->allowed_proxies, "all"); } /* If we have an allow "all" then the denied list becomes a * "mask" meaning "allow everything except these". Otherwise we just * remove the IPs from the allowed list. */ if (ap_table_get(merged->allowed_proxies, "all")) { merged->denied_proxies = ap_overlay_tables(p, child->denied_proxies, merged->denied_proxies); } else { ap_table_do(take_out_proxies, (void*)merged->allowed_proxies, child->denied_proxies, NULL); } /* Now handle the allows, which is easy for a change. */ merged->allowed_proxies = ap_overlay_tables(p, child->allowed_proxies, merged->allowed_proxies); /* If we altered the child tables then set them back. */ if (altered_child_denied) ap_table_set(child->denied_proxies, "all", "t"); if (altered_child_allowed) ap_table_set(child->allowed_proxies, "all", "t"); return (void*)merged; } /* Make sure the given proxy IP is allowed with the conf we are given. */ static int proxy_is_kosher(fwd_dir_conf *conf, char *proxy_ip) { /* If the allowed list is set to "all", then we "mask out" any * proxies that might be in the denied list. */ if (ap_table_get(conf->allowed_proxies, "all")) { if (ap_table_get(conf->denied_proxies, proxy_ip)) return 0; } /* Otherwise we just need to make sure this IP is in the allowed list. */ else if (!ap_table_get(conf->allowed_proxies, proxy_ip)) { /* Oops. It's not. */ return 0; } return 1; } /* Request cleanup handler and associated data structure. * * This restores the remote_ip of the connection over which requests * are being made after a request transaction has completed, if the * conn_rec was changed for that request. This is needed if our * incoming connection is from a proxy server which is making multiple * requests, potentially for different clients, down a persistent * connection. * * If we do not restore the proxy server's IP in the conn_rec then all * subsequent requests down the connection will be misattributed to * the same IP as the first request. */ typedef struct proxy_save_rec proxy_save_rec; struct proxy_save_rec { conn_rec *saved_connection; /* connection record being used */ char *saved_remote_ip; /* original remote_ip */ char *saved_remote_host; /* original remote_host */ }; static void restore_proxy_remote_addr(void *data) { proxy_save_rec *proxy_saved = (proxy_save_rec *)data; conn_rec *conn = proxy_saved->saved_connection; conn->remote_ip = proxy_saved->saved_remote_ip; conn->remote_addr.sin_addr.s_addr = inet_addr(conn->remote_ip); conn->remote_host = proxy_saved->saved_remote_host; } /* If a proxy has provided us with an X-Forwarded-For: header we want * to set the remote IP of the request to the one provided. */ static int real_set_proxy_remote_addr(request_rec *r) { const char *fwded_for; char *val, *client_ip; proxy_save_rec *proxy_saved; fwd_dir_conf *conf; array_header *ary; int ctr, start_ptr; conf = (fwd_dir_conf*)ap_get_module_config(r->per_dir_config, &extract_forwarded_module); if (!(conf->allow_cache)) { ap_table_set(r->headers_out, "Pragma", "no-cache"); ap_table_set(r->headers_out, "Cache-Control", "no-cache"); } /* First make sure the proxy actually making this request is kosher. */ if (! proxy_is_kosher(conf, r->connection->remote_ip)) return OK; /* Okay now let's look for the header. */ if ((fwded_for = ap_table_get(r->headers_in, "X-Forwarded-For")) == NULL && (fwded_for = ap_table_get(r->headers_in, "Forwarded-For")) == NULL) return OK; ary = ap_make_array(r->pool, 1, sizeof(char*)); ctr = 0; while (*fwded_for && (val = ap_get_token(r->pool, &fwded_for, 0))) { *(char**)ap_push_array(ary) = ap_pstrdup(r->pool, val); if (*fwded_for == ',' || *fwded_for == ';') { ++fwded_for; } /* This is a little "anti-suicide" clause if someone tries to * feed us a monster string. */ if (++ctr > 64) break; } /* Scan back from the end of the list of forwarded-fors until we * find one that isn't kosher, that is, it isn't one of our proxy * servers. This allows us to back out any sequence of our proxy * servers and find the first IP that isn't, which is the IP we're * interested in. What we want is the IP number of the machine * that made the connection to first of, potentially a sequence * of, our trusted proxies. We don't care about any external * proxies that may precede our trusted proxies because we cannot * trust what they say. * * Do not search back beyond the 2nd forwarded-for IP number. Even * if the first is from a trusted proxy's IP number it must have * been acting as a client not a proxy if it appears in that * position. */ for (ctr = ary->nelts - 1; ctr >= 1; ctr--) if (! proxy_is_kosher(conf, ((char**)ary->elts)[ctr] )) break; client_ip = ((char**)ary->elts)[ctr]; /* Preserve the proxy's IP etc so we can reset the conn_rec in our * cleanup handler. We pass the saved data in the cleanup handler * registration. */ proxy_saved = ap_pcalloc(r->pool, sizeof(proxy_save_rec)); proxy_saved->saved_connection = r->connection; proxy_saved->saved_remote_ip = r->connection->remote_ip; proxy_saved->saved_remote_host = r->connection->remote_host; ap_register_cleanup(r->pool, (void *)proxy_saved, restore_proxy_remote_addr, ap_null_cleanup); /* Put the proxy IP in an env var so that subsequent modules, or * CGIs, can know who really sent the request (if they care), as * well as on who's behalf. The presence of this var also serves * to tell other modules in other phases (including this module!) * that the conn_rec has already been tampered with so don't do it * again. */ ap_table_set(r->subprocess_env, "PROXY_ADDR", r->connection->remote_ip); /* Here's the spoof. */ r->connection->remote_ip = client_ip; /* To allow .htaccess files to work, we really need to alter this * value as well. - David Hayes */ r->connection->remote_addr.sin_addr.s_addr = inet_addr(client_ip); r->connection->remote_host = ap_pstrdup(r->pool, ap_get_remote_host(r->connection, r->per_dir_config, REMOTE_HOST)); return OK; } /* This is what we export as our handler. It checks for the presence * of PROXY_ADDR and calls real_set_proxy_remote_addr() to do the * work, only if necessary. */ static int set_proxy_remote_addr(request_rec *r) { if (ap_table_get(r->subprocess_env, "PROXY_ADDR") == NULL) real_set_proxy_remote_addr(r); return OK; } /* Yet another wrapper, this one is hooked to the URI translation * phase where the return code needs to be different from above. */ static int ft_set_proxy_remote_addr(request_rec *r) { if (ap_table_get(r->subprocess_env, "PROXY_ADDR") == NULL) real_set_proxy_remote_addr(r); return DECLINED; } /* We used to just hook into post read-request, but post read-request * is not invoked for subrequests and this was tripping us up. (For * example when the proxy and true server are named virtual hosts in * the same Apache instance.) So we hook it up in multiple places and * count on PROXY_ADDR to tell us if we need to really run or not. */ module MODULE_VAR_EXPORT extract_forwarded_module = { STANDARD_MODULE_STUFF, NULL, /* initializer */ create_fwd_dir_conf, /* dir config creater */ merge_fwd_dir_conf, /* dir merger --- default is to override */ NULL, /* server config */ NULL, /* merge server configs */ extract_cmds, /* command table */ NULL, /* handlers */ ft_set_proxy_remote_addr, /* filename translation */ NULL, /* check_user_id */ NULL, /* check auth */ NULL, /* check access */ NULL, /* type_checker */ NULL, /* fixups */ NULL, /* logger */ set_proxy_remote_addr, /* header parser */ NULL, /* child_init */ NULL, /* child_exit */ set_proxy_remote_addr /* post read-request */ };