1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2  * contributor license agreements.  See the NOTICE file distributed with
3  * this work for additional information regarding copyright ownership.
4  * The ASF licenses this file to You under the Apache License, Version 2.0
5  * (the "License"); you may not use this file except in compliance with
6  * the License.  You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  * Derived from mod_remoteip.c.
17  * Default values for directives are hard-wired for CloudFlare defaults.
18  *
19  * Supported directives and example values (further info in README):
20  *
21  * CloudFlareRemoteIPHeader CF-Connecting-IP
22  * CloudFlareRemoteIPTrustedProxy 204.93.173.0/24
23  * DenyAllButCloudFlare
24  *
25  */
26 
27 #include "ap_config.h"
28 #include "ap_mmn.h"
29 #include "httpd.h"
30 #include "http_config.h"
31 #include "http_connection.h"
32 #include "http_protocol.h"
33 #include "http_log.h"
34 #include "apr_strings.h"
35 #include "apr_lib.h"
36 #define APR_WANT_BYTEFUNC
37 #include "apr_want.h"
38 #include "apr_network_io.h"
39 
40 module AP_MODULE_DECLARE_DATA cloudflare_module;
41 
42 #define CF_DEFAULT_IP_HEADER "CF-Connecting-IP"
43 /* CloudFlare IP Ranges from https://www.cloudflare.com/ips */
44 static const char* CF_DEFAULT_TRUSTED_PROXY[] = {
45 /* IPv4 Address Ranges */
46   "103.21.244.0/22",
47   "103.22.200.0/22",
48   "103.31.4.0/22",
49   "104.16.0.0/12",
50   "108.162.192.0/18",
51   "131.0.72.0/22",
52   "141.101.64.0/18",
53   "162.158.0.0/15",
54   "172.64.0.0/13",
55   "173.245.48.0/20",
56   "188.114.96.0/20",
57   "190.93.240.0/20",
58   "197.234.240.0/22",
59   "198.41.128.0/17",
60 /* IPv6 Address Ranges */
61   "2400:cb00::/32",
62   "2405:8100::/32",
63   "2405:b500::/32",
64   "2606:4700::/32",
65   "2803:f800::/32",
66   "2c0f:f248::/32",
67   "2a06:98c0::/29",
68 };
69 static const size_t CF_DEFAULT_TRUSTED_PROXY_COUNT =
70   sizeof(CF_DEFAULT_TRUSTED_PROXY)/sizeof(char *);
71 
72 typedef struct {
73     /** A proxy IP mask to match */
74     apr_ipsubnet_t *ip;
75     /** Flagged if internal, otherwise an external trusted proxy */
76     void  *internal;
77 } cloudflare_proxymatch_t;
78 
79 typedef struct {
80     /** The header to retrieve a proxy-via ip list */
81     const char *header_name;
82     /** A header to record the proxied IP's
83      * (removed as the physical connection and
84      * from the proxy-via ip header value list)
85      */
86     const char *proxies_header_name;
87     /** A list of trusted proxies, ideally configured
88      *  with the most commonly encountered listed first
89      */
90 
91     int deny_all;
92     /** If this flag is set, only allow requests which originate from a CF Trusted Proxy IP.
93      * Return 403 otherwise.
94      */
95     apr_array_header_t *proxymatch_ip;
96 } cloudflare_config_t;
97 
98 typedef struct {
99     /** The previous proxy-via request header value */
100     const char *prior_remote;
101     /** The unmodified original ip and address */
102     const char *orig_ip;
103     apr_sockaddr_t *orig_addr;
104     /** The list of proxy ip's ignored as remote ip's */
105     const char *proxy_ips;
106     /** The remaining list of untrusted proxied remote ip's */
107     const char *proxied_remote;
108     /** The most recently modified ip and address record */
109     const char *proxied_ip;
110     apr_sockaddr_t proxied_addr;
111 } cloudflare_conn_t;
112 
113 static apr_status_t set_cf_default_proxies(apr_pool_t *p, cloudflare_config_t *config);
114 
create_cloudflare_server_config(apr_pool_t * p,server_rec * s)115 static void *create_cloudflare_server_config(apr_pool_t *p, server_rec *s)
116 {
117     cloudflare_config_t *config = apr_pcalloc(p, sizeof *config);
118     /* config->header_name = NULL;
119      * config->proxies_header_name = NULL;
120      */
121     if (config == NULL) {
122         return NULL;
123     }
124     if (set_cf_default_proxies(p, config) != APR_SUCCESS) {
125         return NULL;
126     }
127     config->header_name = CF_DEFAULT_IP_HEADER;
128     return config;
129 }
130 
merge_cloudflare_server_config(apr_pool_t * p,void * globalv,void * serverv)131 static void *merge_cloudflare_server_config(apr_pool_t *p, void *globalv,
132                                             void *serverv)
133 {
134     cloudflare_config_t *global = (cloudflare_config_t *) globalv;
135     cloudflare_config_t *server = (cloudflare_config_t *) serverv;
136     cloudflare_config_t *config;
137 
138     config = (cloudflare_config_t *) apr_palloc(p, sizeof(*config));
139     config->header_name = server->header_name
140                         ? server->header_name
141                         : global->header_name;
142     config->proxies_header_name = server->proxies_header_name
143                                 ? server->proxies_header_name
144                                 : global->proxies_header_name;
145     config->proxymatch_ip = server->proxymatch_ip
146                           ? server->proxymatch_ip
147                           : global->proxymatch_ip;
148     return config;
149 }
150 
header_name_set(cmd_parms * cmd,void * dummy,const char * arg)151 static const char *header_name_set(cmd_parms *cmd, void *dummy,
152                                    const char *arg)
153 {
154     cloudflare_config_t *config = ap_get_module_config(cmd->server->module_config,
155                                                        &cloudflare_module);
156     config->header_name = apr_pstrdup(cmd->pool, arg);
157     return NULL;
158 }
159 
deny_all_set(cmd_parms * cmd,void * dummy)160 static const char *deny_all_set(cmd_parms *cmd, void *dummy)
161 {
162     cloudflare_config_t *config = ap_get_module_config(cmd->server->module_config,
163                                                        &cloudflare_module);
164     config->deny_all = 1;
165     return NULL;
166 }
167 
168 /* Would be quite nice if APR exported this */
169 /* apr:network_io/unix/sockaddr.c */
looks_like_ip(const char * ipstr)170 static int looks_like_ip(const char *ipstr)
171 {
172     if (ap_strchr_c(ipstr, ':')) {
173         /* definitely not a hostname; assume it is intended to be an IPv6 address */
174         return 1;
175     }
176 
177     /* simple IPv4 address string check */
178     while ((*ipstr == '.') || apr_isdigit(*ipstr))
179         ipstr++;
180     return (*ipstr == '\0');
181 }
182 
set_cf_default_proxies(apr_pool_t * p,cloudflare_config_t * config)183 static apr_status_t set_cf_default_proxies(apr_pool_t *p, cloudflare_config_t *config)
184 {
185      apr_status_t rv;
186      cloudflare_proxymatch_t *match;
187      int i;
188      char **proxies = CF_DEFAULT_TRUSTED_PROXY;
189 
190      for (i=0; i<CF_DEFAULT_TRUSTED_PROXY_COUNT; i++) {
191          char *ip = apr_pstrdup(p, proxies[i]);
192          char *s = ap_strchr(ip, '/');
193 
194          if (s) {
195              *s++ = '\0';
196          }
197          if (!config->proxymatch_ip) {
198              config->proxymatch_ip = apr_array_make(p, 1, sizeof(*match));
199          }
200 
201          match = (cloudflare_proxymatch_t *) apr_array_push(config->proxymatch_ip);
202          rv = apr_ipsubnet_create(&match->ip, ip, s, p);
203      }
204      return rv;
205 }
206 
proxies_set(cmd_parms * cmd,void * internal,const char * arg)207 static const char *proxies_set(cmd_parms *cmd, void *internal,
208                                const char *arg)
209 {
210     cloudflare_config_t *config = ap_get_module_config(cmd->server->module_config,
211                                                        &cloudflare_module);
212     cloudflare_proxymatch_t *match;
213     apr_status_t rv;
214     char *ip = apr_pstrdup(cmd->temp_pool, arg);
215     char *s = ap_strchr(ip, '/');
216     if (s)
217         *s++ = '\0';
218 
219     if (!config->proxymatch_ip)
220         config->proxymatch_ip = apr_array_make(cmd->pool, 1, sizeof(*match));
221     match = (cloudflare_proxymatch_t *) apr_array_push(config->proxymatch_ip);
222     match->internal = internal;
223 
224     if (looks_like_ip(ip)) {
225         /* Note s may be null, that's fine (explicit host) */
226         rv = apr_ipsubnet_create(&match->ip, ip, s, cmd->pool);
227     }
228     else
229     {
230         apr_sockaddr_t *temp_sa;
231 
232         if (s) {
233             return apr_pstrcat(cmd->pool, "RemoteIP: Error parsing IP ", arg,
234                                " the subnet /", s, " is invalid for ",
235                                cmd->cmd->name, NULL);
236         }
237 
238         rv = apr_sockaddr_info_get(&temp_sa,  ip, APR_UNSPEC, 0,
239                                    APR_IPV4_ADDR_OK, cmd->temp_pool);
240         while (rv == APR_SUCCESS)
241         {
242             apr_sockaddr_ip_get(&ip, temp_sa);
243             rv = apr_ipsubnet_create(&match->ip, ip, NULL, cmd->pool);
244             if (!(temp_sa = temp_sa->next))
245                 break;
246             match = (cloudflare_proxymatch_t *)
247                     apr_array_push(config->proxymatch_ip);
248             match->internal = internal;
249         }
250     }
251 
252     if (rv != APR_SUCCESS) {
253         char msgbuf[128];
254         apr_strerror(rv, msgbuf, sizeof(msgbuf));
255         return apr_pstrcat(cmd->pool, "RemoteIP: Error parsing IP ", arg,
256                            " (", msgbuf, " error) for ", cmd->cmd->name, NULL);
257     }
258 
259     return NULL;
260 }
261 
cloudflare_modify_connection(request_rec * r)262 static int cloudflare_modify_connection(request_rec *r)
263 {
264     conn_rec *c = r->connection;
265     cloudflare_config_t *config = (cloudflare_config_t *)
266         ap_get_module_config(r->server->module_config, &cloudflare_module);
267 
268     cloudflare_conn_t *conn;
269 #ifdef REMOTEIP_OPTIMIZED
270     apr_sockaddr_t temp_sa_buff;
271     apr_sockaddr_t *temp_sa = &temp_sa_buff;
272 #else
273     apr_sockaddr_t *temp_sa;
274 #endif
275     apr_status_t rv;
276     char *remote = (char *) apr_table_get(r->headers_in, config->header_name);
277     char *proxy_ips = NULL;
278     char *parse_remote;
279     char *eos;
280     unsigned char *addrbyte;
281     void *internal = NULL;
282     const char *cf_visitor_header = NULL;
283 
284     apr_pool_userdata_get((void*)&conn, "mod_cloudflare-conn", c->pool);
285 
286     cf_visitor_header = apr_table_get(r->headers_in, "CF-Visitor");
287     if (cf_visitor_header != NULL) {
288         if ((remote) && (strstr(cf_visitor_header, "https") != NULL)) {
289             apr_table_t *e = r->subprocess_env;
290             apr_table_addn(e, "HTTPS", "on");
291         }
292     }
293 
294     if (conn) {
295         if (remote && (strcmp(remote, conn->prior_remote) == 0)) {
296             /* TODO: Recycle r-> overrides from previous request
297              */
298             goto ditto_request_rec;
299         }
300         else {
301             /* TODO: Revert connection from previous request
302              */
303 #if AP_MODULE_MAGIC_AT_LEAST(20111130,0)
304             c->client_addr = conn->orig_addr;
305             c->client_ip = (char *) conn->orig_ip;
306 #else
307             c->remote_addr = conn->orig_addr;
308             c->remote_ip = (char *) conn->orig_ip;
309 #endif
310         }
311     }
312 
313     /* Deny requests that do not have a CloudFlareRemoteIPHeader set when
314      * DenyAllButCloudFlare is set. Do not modify the request otherwise and
315      * return early.
316      */
317     if (!remote) {
318         if (config->deny_all) {
319             return 403;
320         }
321 
322         return OK;
323     }
324 
325     remote = apr_pstrdup(r->pool, remote);
326 
327 #if AP_MODULE_MAGIC_AT_LEAST(20111130,0)
328 
329 #ifdef REMOTEIP_OPTIMIZED
330     memcpy(temp_sa, c->client_addr, sizeof(*temp_sa));
331     temp_sa->pool = r->pool;
332 #else
333     temp_sa = c->client_addr;
334 #endif
335 
336 #else
337 
338 #ifdef REMOTEIP_OPTIMIZED
339     memcpy(temp_sa, c->remote_addr, sizeof(*temp_sa));
340     temp_sa->pool = r->pool;
341 #else
342     temp_sa = c->remote_addr;
343 #endif
344 
345 #endif
346 
347     while (remote) {
348 
349         /* verify c->client_addr is trusted if there is a trusted proxy list
350          */
351         if (config->proxymatch_ip) {
352             int i;
353             cloudflare_proxymatch_t *match;
354             match = (cloudflare_proxymatch_t *)config->proxymatch_ip->elts;
355             for (i = 0; i < config->proxymatch_ip->nelts; ++i) {
356 #if AP_MODULE_MAGIC_AT_LEAST(20111130,0)
357                 if (apr_ipsubnet_test(match[i].ip, c->client_addr)) {
358                     internal = match[i].internal;
359                     break;
360                 }
361 #else
362                 if (apr_ipsubnet_test(match[i].ip, c->remote_addr)) {
363                     internal = match[i].internal;
364                     break;
365                 }
366 #endif
367             }
368             if (i && i >= config->proxymatch_ip->nelts) {
369                 if (config->deny_all) {
370                     return 403;
371                 } else {
372                     break;
373                 }
374             }
375         }
376 
377         if ((parse_remote = strrchr(remote, ',')) == NULL) {
378             parse_remote = remote;
379             remote = NULL;
380         }
381         else {
382             *(parse_remote++) = '\0';
383         }
384 
385         while (*parse_remote == ' ')
386             ++parse_remote;
387 
388         eos = parse_remote + strlen(parse_remote) - 1;
389         while (eos >= parse_remote && *eos == ' ')
390             *(eos--) = '\0';
391 
392         if (eos < parse_remote) {
393             if (remote)
394                 *(remote + strlen(remote)) = ',';
395             else
396                 remote = parse_remote;
397             break;
398         }
399 
400 #ifdef REMOTEIP_OPTIMIZED
401         /* Decode client_addr - sucks; apr_sockaddr_vars_set isn't 'public' */
402         if (inet_pton(AF_INET, parse_remote,
403                       &temp_sa->sa.sin.sin_addr) > 0) {
404             apr_sockaddr_vars_set(temp_sa, APR_INET, temp_sa.port);
405         }
406 #if APR_HAVE_IPV6
407         else if (inet_pton(AF_INET6, parse_remote,
408                            &temp_sa->sa.sin6.sin6_addr) > 0) {
409             apr_sockaddr_vars_set(temp_sa, APR_INET6, temp_sa.port);
410         }
411 #endif
412         else {
413             rv = apr_get_netos_error();
414 #else /* !REMOTEIP_OPTIMIZED */
415         /* We map as IPv4 rather than IPv6 for equivilant host names
416          * or IPV4OVERIPV6
417          */
418         rv = apr_sockaddr_info_get(&temp_sa,  parse_remote,
419                                    APR_UNSPEC, temp_sa->port,
420                                    APR_IPV4_ADDR_OK, r->pool);
421         if (rv != APR_SUCCESS) {
422 #endif
423             ap_log_rerror(APLOG_MARK, APLOG_DEBUG,  rv, r,
424                           "RemoteIP: Header %s value of %s cannot be parsed "
425                           "as a client IP",
426                           config->header_name, parse_remote);
427             if (remote)
428                 *(remote + strlen(remote)) = ',';
429             else
430                 remote = parse_remote;
431             break;
432         }
433 
434         addrbyte = (unsigned char *) &temp_sa->sa.sin.sin_addr;
435 
436         /* For intranet (Internal proxies) ignore all restrictions below */
437         if (!internal
438               && ((temp_sa->family == APR_INET
439                    /* For internet (non-Internal proxies) deny all
440                     * RFC3330 designated local/private subnets:
441                     * 10.0.0.0/8   169.254.0.0/16  192.168.0.0/16
442                     * 127.0.0.0/8  172.16.0.0/12
443                     */
444                       && (addrbyte[0] == 10
445                        || addrbyte[0] == 127
446                        || (addrbyte[0] == 169 && addrbyte[1] == 254)
447                        || (addrbyte[0] == 172 && (addrbyte[1] & 0xf0) == 16)
448                        || (addrbyte[0] == 192 && addrbyte[1] == 168)))
449 #if APR_HAVE_IPV6
450                || (temp_sa->family == APR_INET6
451                    /* For internet (non-Internal proxies) we translated
452                     * IPv4-over-IPv6-mapped addresses as IPv4, above.
453                     * Accept only Global Unicast 2000::/3 defined by RFC4291
454                     */
455                       && ((temp_sa->sa.sin6.sin6_addr.s6_addr[0] & 0xe0) != 0x20))
456 #endif
457         )) {
458             ap_log_rerror(APLOG_MARK, APLOG_DEBUG,  rv, r,
459                           "RemoteIP: Header %s value of %s appears to be "
460                           "a private IP or nonsensical.  Ignored",
461                           config->header_name, parse_remote);
462             if (remote)
463                 *(remote + strlen(remote)) = ',';
464             else
465                 remote = parse_remote;
466             break;
467         }
468 
469 #if AP_MODULE_MAGIC_AT_LEAST(20111130,0)
470         if (!conn) {
471             conn = (cloudflare_conn_t *) apr_palloc(c->pool, sizeof(*conn));
472             apr_pool_userdata_set(conn, "mod_cloudflare-conn", NULL, c->pool);
473             conn->orig_addr = c->client_addr;
474             conn->orig_ip = c->client_ip;
475         }
476         /* Set remote_ip string */
477         if (!internal) {
478             if (proxy_ips)
479                 proxy_ips = apr_pstrcat(r->pool, proxy_ips, ", ",
480                                         c->client_ip, NULL);
481             else
482                 proxy_ips = c->client_ip;
483         }
484 
485         c->client_addr = temp_sa;
486         apr_sockaddr_ip_get(&c->client_ip, c->client_addr);
487     }
488 
489     /* Nothing happened? */
490     if (!conn || (c->client_addr == conn->orig_addr))
491         return OK;
492 
493     /* Fixups here, remote becomes the new Via header value, etc
494      * In the heavy operations above we used request scope, to limit
495      * conn pool memory growth on keepalives, so here we must scope
496      * the final results to the connection pool lifetime.
497      * To limit memory growth, we keep recycling the same buffer
498      * for the final apr_sockaddr_t in the remoteip conn rec.
499      */
500     c->client_ip = apr_pstrdup(c->pool, c->client_ip);
501     conn->proxied_ip = c->client_ip;
502 
503     r->useragent_ip = c->client_ip;
504     r->useragent_addr = c->client_addr;
505 
506     memcpy(&conn->proxied_addr, temp_sa, sizeof(*temp_sa));
507     conn->proxied_addr.pool = c->pool;
508     c->client_addr = &conn->proxied_addr;
509 #else
510         if (!conn) {
511             conn = (cloudflare_conn_t *) apr_palloc(c->pool, sizeof(*conn));
512             apr_pool_userdata_set(conn, "mod_cloudflare-conn", NULL, c->pool);
513             conn->orig_addr = c->remote_addr;
514             conn->orig_ip = c->remote_ip;
515         }
516 
517         /* Set remote_ip string */
518         if (!internal) {
519             if (proxy_ips)
520                 proxy_ips = apr_pstrcat(r->pool, proxy_ips, ", ",
521                                         c->remote_ip, NULL);
522             else
523                 proxy_ips = c->remote_ip;
524         }
525 
526         c->remote_addr = temp_sa;
527         apr_sockaddr_ip_get(&c->remote_ip, c->remote_addr);
528     }
529 
530     /* Nothing happened? */
531     if (!conn || (c->remote_addr == conn->orig_addr))
532         return OK;
533 
534     /* Fixups here, remote becomes the new Via header value, etc
535      * In the heavy operations above we used request scope, to limit
536      * conn pool memory growth on keepalives, so here we must scope
537      * the final results to the connection pool lifetime.
538      * To limit memory growth, we keep recycling the same buffer
539      * for the final apr_sockaddr_t in the remoteip conn rec.
540      */
541     c->remote_ip = apr_pstrdup(c->pool, c->remote_ip);
542     conn->proxied_ip = c->remote_ip;
543     memcpy(&conn->proxied_addr, temp_sa, sizeof(*temp_sa));
544     conn->proxied_addr.pool = c->pool;
545     c->remote_addr = &conn->proxied_addr;
546 #endif
547 
548     if (remote)
549         remote = apr_pstrdup(c->pool, remote);
550     conn->proxied_remote = remote;
551     conn->prior_remote = apr_pstrdup(c->pool, apr_table_get(r->headers_in,
552                                                       config->header_name));
553     if (proxy_ips)
554         proxy_ips = apr_pstrdup(c->pool, proxy_ips);
555     conn->proxy_ips = proxy_ips;
556 
557     /* Unset remote_host string DNS lookups */
558     c->remote_host = NULL;
559     c->remote_logname = NULL;
560 
561 ditto_request_rec:
562 
563     if (conn->proxy_ips) {
564         apr_table_setn(r->notes, "cloudflare-proxy-ip-list", conn->proxy_ips);
565         if (config->proxies_header_name)
566             apr_table_setn(r->headers_in, config->proxies_header_name,
567                            conn->proxy_ips);
568     }
569 
570     ap_log_rerror(APLOG_MARK, APLOG_INFO|APLOG_NOERRNO, 0, r,
571                   conn->proxy_ips
572                       ? "Using %s as client's IP by proxies %s"
573                       : "Using %s as client's IP by internal proxies",
574                   conn->proxied_ip, conn->proxy_ips);
575     return OK;
576 }
577 
578 static const command_rec cloudflare_cmds[] =
579 {
580     AP_INIT_TAKE1("CloudFlareRemoteIPHeader", header_name_set, NULL, RSRC_CONF,
581                   "Specifies a request header to trust as the client IP, "
582                   "Overrides the default of CF-Connecting-IP"),
583     AP_INIT_ITERATE("CloudFlareRemoteIPTrustedProxy", proxies_set, 0, RSRC_CONF,
584                     "Specifies one or more proxies which are trusted "
585                     "to present IP headers. Overrides the defaults."),
586     AP_INIT_NO_ARGS("DenyAllButCloudFlare", deny_all_set, NULL, RSRC_CONF,
587                     "Return a 403 status to all requests which do not originate from "
588                     "a CloudFlareRemoteIPTrustedProxy."),
589     { NULL }
590 };
591 
register_hooks(apr_pool_t * p)592 static void register_hooks(apr_pool_t *p)
593 {
594     // We need to run very early so as to not trip up mod_security.
595     // Hence, this little trick, as mod_security runs at APR_HOOK_REALLY_FIRST.
596     ap_hook_post_read_request(cloudflare_modify_connection, NULL, NULL, APR_HOOK_REALLY_FIRST - 10);
597 }
598 
599 module AP_MODULE_DECLARE_DATA cloudflare_module = {
600     STANDARD20_MODULE_STUFF,
601     NULL,                            /* create per-directory config structure */
602     NULL,                            /* merge per-directory config structures */
603     create_cloudflare_server_config, /* create per-server config structure */
604     merge_cloudflare_server_config,  /* merge per-server config structures */
605     cloudflare_cmds,                 /* command apr_table_t */
606     register_hooks                   /* register hooks */
607 };
608