7 #include "inet_ntop_cache.h"
11 #include "configfile.h"
17 #include "sys-socket.h"
20 * mod_extforward.c for lighttpd, by comman.kang <at> gmail <dot> com
21 * extended, modified by Lionel Elie Mamane (LEM), lionel <at> mamane <dot> lu
22 * support chained proxies by glen@delfi.ee, #1528
26 * Trust proxy 10.0.0.232 and 10.0.0.232
27 * extforward.forwarder = ( "10.0.0.232" => "trust",
28 * "10.0.0.233" => "trust" )
30 * Trust all proxies (NOT RECOMMENDED!)
31 * extforward.forwarder = ( "all" => "trust")
33 * Note that "all" has precedence over specific entries,
34 * so "all except" setups will not work.
36 * In case you have chained proxies, you can add all their IP's to the
37 * config. However "all" has effect only on connecting IP, as the
38 * X-Forwarded-For header can not be trusted.
40 * Note: The effect of this module is variable on $HTTP["remotip"] directives and
41 * other module's remote ip dependent actions.
42 * Things done by modules before we change the remoteip or after we reset it will match on the proxy's IP.
43 * Things done in between these two moments will match on the real client's IP.
44 * The moment things are done by a module depends on in which hook it does things and within the same hook
45 * on whether they are before/after us in the module loading order
46 * (order in the server.modules directive in the config file).
50 * mod_access: Will match on the real client.
53 * In order to see the "real" ip address in access log ,
54 * you'll have to load mod_extforward after mod_accesslog.
65 /* plugin config for all request/connections */
68 PROXY_FORWARDED_NONE
= 0x00,
69 PROXY_FORWARDED_FOR
= 0x01,
70 PROXY_FORWARDED_PROTO
= 0x02,
71 PROXY_FORWARDED_HOST
= 0x04,
72 PROXY_FORWARDED_BY
= 0x08,
73 PROXY_FORWARDED_REMOTE_USER
= 0x10
81 unsigned short int hap_PROXY
;
82 unsigned short int hap_PROXY_ssl_client_verify
;
88 plugin_config
**config_storage
;
93 static plugin_data
*mod_extforward_plugin_data_singleton
;
94 static int extforward_check_proxy
;
97 /* context , used for restore remote ip */
100 /* per-request state */
101 sock_addr saved_remote_addr
;
102 buffer
*saved_remote_addr_buf
;
104 /* hap-PROXY protocol prior to receiving first request */
105 int(*saved_network_read
)(server
*, connection
*, chunkqueue
*, off_t
);
107 /* connection-level state applied to requests in handle_request_env */
109 int ssl_client_verify
;
113 static handler_ctx
* handler_ctx_init(void) {
115 hctx
= calloc(1, sizeof(*hctx
));
119 static void handler_ctx_free(handler_ctx
*hctx
) {
123 /* init the plugin data */
124 INIT_FUNC(mod_extforward_init
) {
126 p
= calloc(1, sizeof(*p
));
127 mod_extforward_plugin_data_singleton
= p
;
131 /* destroy the plugin data */
132 FREE_FUNC(mod_extforward_free
) {
133 plugin_data
*p
= p_d
;
137 if (!p
) return HANDLER_GO_ON
;
139 if (p
->config_storage
) {
142 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
143 plugin_config
*s
= p
->config_storage
[i
];
145 if (NULL
== s
) continue;
147 array_free(s
->forwarder
);
148 array_free(s
->headers
);
149 array_free(s
->opts_params
);
153 free(p
->config_storage
);
159 return HANDLER_GO_ON
;
162 /* handle plugin config and check values */
164 SETDEFAULTS_FUNC(mod_extforward_set_defaults
) {
165 plugin_data
*p
= p_d
;
168 config_values_t cv
[] = {
169 { "extforward.forwarder", NULL
, T_CONFIG_ARRAY
, T_CONFIG_SCOPE_CONNECTION
}, /* 0 */
170 { "extforward.headers", NULL
, T_CONFIG_ARRAY
, T_CONFIG_SCOPE_CONNECTION
}, /* 1 */
171 { "extforward.params", NULL
, T_CONFIG_ARRAY
, T_CONFIG_SCOPE_CONNECTION
}, /* 2 */
172 { "extforward.hap-PROXY", NULL
, T_CONFIG_BOOLEAN
, T_CONFIG_SCOPE_CONNECTION
}, /* 3 */
173 { "extforward.hap-PROXY-ssl-client-verify", NULL
, T_CONFIG_BOOLEAN
, T_CONFIG_SCOPE_CONNECTION
}, /* 4 */
174 { NULL
, NULL
, T_CONFIG_UNSET
, T_CONFIG_SCOPE_UNSET
}
177 if (!p
) return HANDLER_ERROR
;
179 p
->config_storage
= calloc(1, srv
->config_context
->used
* sizeof(plugin_config
*));
181 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
182 data_config
const* config
= (data_config
const*)srv
->config_context
->data
[i
];
185 s
= calloc(1, sizeof(plugin_config
));
186 s
->forwarder
= array_init();
187 s
->headers
= array_init();
188 s
->opts_params
= array_init();
189 s
->opts
= PROXY_FORWARDED_NONE
;
191 cv
[0].destination
= s
->forwarder
;
192 cv
[1].destination
= s
->headers
;
193 cv
[2].destination
= s
->opts_params
;
194 cv
[3].destination
= &s
->hap_PROXY
;
195 cv
[4].destination
= &s
->hap_PROXY_ssl_client_verify
;
197 p
->config_storage
[i
] = s
;
199 if (0 != config_insert_values_global(srv
, config
->value
, cv
, i
== 0 ? T_CONFIG_SCOPE_SERVER
: T_CONFIG_SCOPE_CONNECTION
)) {
200 return HANDLER_ERROR
;
203 if (!array_is_kvstring(s
->forwarder
)) {
204 log_error_write(srv
, __FILE__
, __LINE__
, "s",
205 "unexpected value for extforward.forwarder; expected list of \"IPaddr\" => \"trust\"");
206 return HANDLER_ERROR
;
209 if (!array_is_vlist(s
->headers
)) {
210 log_error_write(srv
, __FILE__
, __LINE__
, "s",
211 "unexpected value for extforward.headers; expected list of \"headername\"");
212 return HANDLER_ERROR
;
215 /* default to "X-Forwarded-For" or "Forwarded-For" if extforward.headers not specified or empty */
216 if (!s
->hap_PROXY
&& 0 == s
->headers
->used
&& (0 == i
|| NULL
!= array_get_element(config
->value
, "extforward.headers"))) {
218 ds
= data_string_init();
219 buffer_copy_string_len(ds
->value
, CONST_STR_LEN("X-Forwarded-For"));
220 array_insert_unique(s
->headers
, (data_unset
*)ds
);
221 ds
= data_string_init();
222 buffer_copy_string_len(ds
->value
, CONST_STR_LEN("Forwarded-For"));
223 array_insert_unique(s
->headers
, (data_unset
*)ds
);
226 if (!array_is_kvany(s
->opts_params
)) {
227 log_error_write(srv
, __FILE__
, __LINE__
, "s",
228 "unexpected value for extforward.params; expected ( \"param\" => \"value\" )");
229 return HANDLER_ERROR
;
231 for (size_t j
= 0, used
= s
->opts_params
->used
; j
< used
; ++j
) {
232 proxy_forwarded_t param
;
233 data_unset
*du
= s
->opts_params
->data
[j
];
234 #if 0 /*("for" and "proto" historical behavior: always enabled)*/
235 if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("by"))) {
236 param
= PROXY_FORWARDED_BY
;
237 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("for"))) {
238 param
= PROXY_FORWARDED_FOR
;
241 if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("host"))) {
242 param
= PROXY_FORWARDED_HOST
;
244 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("proto"))) {
245 param
= PROXY_FORWARDED_PROTO
;
247 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("remote_user"))) {
248 param
= PROXY_FORWARDED_REMOTE_USER
;
250 log_error_write(srv
, __FILE__
, __LINE__
, "sb",
251 "extforward.params keys must be one of: host, remote_user, but not:", du
->key
);
252 return HANDLER_ERROR
;
254 if (du
->type
== TYPE_STRING
) {
255 data_string
*ds
= (data_string
*)du
;
256 if (buffer_is_equal_string(ds
->value
, CONST_STR_LEN("enable"))) {
258 } else if (!buffer_is_equal_string(ds
->value
, CONST_STR_LEN("disable"))) {
259 log_error_write(srv
, __FILE__
, __LINE__
, "sb",
260 "extforward.params values must be one of: 0, 1, enable, disable; error for key:", du
->key
);
261 return HANDLER_ERROR
;
263 } else if (du
->type
== TYPE_INTEGER
) {
264 data_integer
*di
= (data_integer
*)du
;
265 if (di
->value
) s
->opts
|= param
;
267 log_error_write(srv
, __FILE__
, __LINE__
, "sb",
268 "extforward.params values must be one of: 0, 1, enable, disable; error for key:", du
->key
);
269 return HANDLER_ERROR
;
274 /* attempt to warn if mod_extforward is not last module loaded to hook
275 * handle_connection_accept. (Nice to have, but remove this check if
276 * it reaches too far into internals and prevents other code changes.)
277 * While it would be nice to check connection_handle_accept plugin slot
278 * to make sure mod_extforward is last, that info is private to plugin.c
279 * so merely warn if mod_openssl is loaded after mod_extforward, though
280 * future modules which hook connection_handle_accept might be missed.*/
281 for (i
= 0; i
< srv
->config_context
->used
; ++i
) {
282 plugin_config
*s
= p
->config_storage
[i
];
285 for (j
= 0; j
< srv
->srvconf
.modules
->used
; ++j
) {
286 data_string
*ds
= (data_string
*)srv
->srvconf
.modules
->data
[j
];
287 if (buffer_is_equal_string(ds
->value
, CONST_STR_LEN("mod_extforward"))) {
291 for (; j
< srv
->srvconf
.modules
->used
; ++j
) {
292 data_string
*ds
= (data_string
*)srv
->srvconf
.modules
->data
[j
];
293 if (buffer_is_equal_string(ds
->value
, CONST_STR_LEN("mod_openssl"))) {
294 log_error_write(srv
, __FILE__
, __LINE__
, "s",
295 "mod_extforward must be loaded after mod_openssl in server.modules when extforward.hap-PROXY = \"enable\"");
303 for (i
= 0; i
< srv
->srvconf
.modules
->used
; i
++) {
304 data_string
*ds
= (data_string
*)srv
->srvconf
.modules
->data
[i
];
305 if (buffer_is_equal_string(ds
->value
, CONST_STR_LEN("mod_proxy"))) {
306 extforward_check_proxy
= 1;
311 return HANDLER_GO_ON
;
316 static int mod_extforward_patch_connection(server
*srv
, connection
*con
, plugin_data
*p
) {
318 plugin_config
*s
= p
->config_storage
[0];
324 PATCH(hap_PROXY_ssl_client_verify
);
326 /* skip the first, the global context */
327 for (i
= 1; i
< srv
->config_context
->used
; i
++) {
328 data_config
*dc
= (data_config
*)srv
->config_context
->data
[i
];
329 s
= p
->config_storage
[i
];
331 /* condition didn't match */
332 if (!config_check_cond(srv
, con
, dc
)) continue;
335 for (j
= 0; j
< dc
->value
->used
; j
++) {
336 data_unset
*du
= dc
->value
->data
[j
];
338 if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("extforward.forwarder"))) {
340 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("extforward.headers"))) {
342 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("extforward.params"))) {
344 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("extforward.hap-PROXY"))) {
346 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("extforward.hap-PROXY-ssl-client-verify"))) {
347 PATCH(hap_PROXY_ssl_client_verify
);
357 static void put_string_into_array_len(array
*ary
, const char *str
, int len
)
359 data_string
*tempdata
;
362 tempdata
= data_string_init();
363 buffer_copy_string_len(tempdata
->value
,str
,len
);
364 array_insert_unique(ary
,(data_unset
*)tempdata
);
367 extract a forward array from the environment
369 static array
*extract_forward_array(buffer
*pbuffer
)
371 array
*result
= array_init();
372 if (!buffer_string_is_empty(pbuffer
)) {
374 /* state variable, 0 means not in string, 1 means in string */
376 for (base
= pbuffer
->ptr
, curr
= pbuffer
->ptr
; *curr
; curr
++) {
378 if ((*curr
> '9' || *curr
< '0') && *curr
!= '.' && *curr
!= ':' && (*curr
< 'a' || *curr
> 'f') && (*curr
< 'A' || *curr
> 'F')) {
379 /* found an separator , insert value into result array */
380 put_string_into_array_len(result
, base
, curr
- base
);
381 /* change state to not in string */
385 if ((*curr
>= '0' && *curr
<= '9') || *curr
== ':' || (*curr
>= 'a' && *curr
<= 'f') || (*curr
>= 'A' && *curr
<= 'F')) {
386 /* found leading char of an IP address, move base pointer and change state */
392 /* if breaking out while in str, we got to the end of string, so add it */
394 put_string_into_array_len(result
, base
, curr
- base
);
401 #define IP_UNTRUSTED 0
403 * check whether ip is trusted, return 1 for trusted , 0 for untrusted
405 static int is_proxy_trusted(const buffer
*ipstr
, plugin_data
*p
)
407 data_string
* allds
= (data_string
*)array_get_element(p
->conf
.forwarder
, "all");
410 if (strcasecmp(allds
->value
->ptr
, "trust") == 0) {
417 return (data_string
*)array_get_element_klen(p
->conf
.forwarder
, CONST_BUF_LEN(ipstr
)) ? IP_TRUSTED
: IP_UNTRUSTED
;
421 * Return last address of proxy that is not trusted.
422 * Do not accept "all" keyword here.
424 static const char *last_not_in_array(array
*a
, plugin_data
*p
)
426 array
*forwarder
= p
->conf
.forwarder
;
429 for (i
= a
->used
- 1; i
>= 0; i
--) {
430 data_string
*ds
= (data_string
*)a
->data
[i
];
431 if (!array_get_element_klen(forwarder
, CONST_BUF_LEN(ds
->value
))) {
432 return ds
->value
->ptr
;
438 static int mod_extforward_set_addr(server
*srv
, connection
*con
, plugin_data
*p
, const char *addr
) {
440 handler_ctx
*hctx
= con
->plugin_ctx
[p
->id
];
442 if (con
->conf
.log_request_handling
) {
443 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "using address:", addr
);
446 sock
.plain
.sa_family
= AF_UNSPEC
;
447 if (1 != sock_addr_from_str_numeric(srv
, &sock
, addr
)) return 0;
448 if (sock
.plain
.sa_family
== AF_UNSPEC
) return 0;
450 /* we found the remote address, modify current connection and save the old address */
452 if (hctx
->saved_remote_addr_buf
) {
453 if (con
->conf
.log_request_handling
) {
454 log_error_write(srv
, __FILE__
, __LINE__
, "s",
455 "-- mod_extforward_uri_handler already patched this connection, resetting state");
457 con
->dst_addr
= hctx
->saved_remote_addr
;
458 buffer_free(con
->dst_addr_buf
);
459 con
->dst_addr_buf
= hctx
->saved_remote_addr_buf
;
460 hctx
->saved_remote_addr_buf
= NULL
;
463 con
->plugin_ctx
[p
->id
] = hctx
= handler_ctx_init();
465 /* save old address */
466 if (extforward_check_proxy
) {
467 array_set_key_value(con
->environment
, CONST_STR_LEN("_L_EXTFORWARD_ACTUAL_FOR"), CONST_BUF_LEN(con
->dst_addr_buf
));
469 hctx
->saved_remote_addr
= con
->dst_addr
;
470 hctx
->saved_remote_addr_buf
= con
->dst_addr_buf
;
471 /* patch connection address */
472 con
->dst_addr
= sock
;
473 con
->dst_addr_buf
= buffer_init_string(addr
);
475 if (con
->conf
.log_request_handling
) {
476 log_error_write(srv
, __FILE__
, __LINE__
, "ss",
477 "patching con->dst_addr_buf for the accesslog:", addr
);
480 /* Now, clean the conf_cond cache, because we may have changed the results of tests */
481 config_cond_cache_reset_item(srv
, con
, COMP_HTTP_REMOTE_IP
);
486 static void mod_extforward_set_proto(server
*srv
, connection
*con
, const char *proto
, size_t protolen
) {
487 if (0 != protolen
&& !buffer_is_equal_caseless_string(con
->uri
.scheme
, proto
, protolen
)) {
488 /* update scheme if X-Forwarded-Proto is set
490 * - Only "http" or "https" are currently accepted since the request to lighttpd currently has to
491 * be HTTP/1.0 or HTTP/1.1 using http or https. If this is changed, then the scheme from this
492 * untrusted header must be checked to contain only alphanumeric characters, and to be a
493 * reasonable length, e.g. < 256 chars.
494 * - con->uri.scheme is not reset in mod_extforward_restore() but is currently not an issues since
495 * con->uri.scheme will be reset by next request. If a new module uses con->uri.scheme in the
496 * handle_request_done hook, then should evaluate if that module should use the forwarded value
497 * (probably) or the original value.
499 if (extforward_check_proxy
) {
500 array_set_key_value(con
->environment
, CONST_STR_LEN("_L_EXTFORWARD_ACTUAL_PROTO"), CONST_BUF_LEN(con
->uri
.scheme
));
502 if (0 == buffer_caseless_compare(proto
, protolen
, CONST_STR_LEN("https"))) {
503 buffer_copy_string_len(con
->uri
.scheme
, CONST_STR_LEN("https"));
504 config_cond_cache_reset_item(srv
, con
, COMP_HTTP_SCHEME
);
505 } else if (0 == buffer_caseless_compare(proto
, protolen
, CONST_STR_LEN("http"))) {
506 buffer_copy_string_len(con
->uri
.scheme
, CONST_STR_LEN("http"));
507 config_cond_cache_reset_item(srv
, con
, COMP_HTTP_SCHEME
);
512 static handler_t
mod_extforward_X_Forwarded_For(server
*srv
, connection
*con
, plugin_data
*p
, buffer
*x_forwarded_for
) {
513 /* build forward_array from forwarded data_string */
514 array
*forward_array
= extract_forward_array(x_forwarded_for
);
515 const char *real_remote_addr
= last_not_in_array(forward_array
, p
);
516 if (real_remote_addr
!= NULL
) { /* parsed */
517 /* get scheme if X-Forwarded-Proto is set
519 * - X-Forwarded-Proto may or may not be set by proxies, even if X-Forwarded-For is set
520 * - X-Forwarded-Proto may be a comma-separated list if there are multiple proxies,
521 * but the historical behavior of the code below only honored it if there was exactly one value
522 * (not done: walking backwards in X-Forwarded-Proto the same num of steps
523 * as in X-Forwarded-For to find proto set by last trusted proxy)
525 data_string
*x_forwarded_proto
= (data_string
*)array_get_element(con
->request
.headers
, "X-Forwarded-Proto");
526 if (mod_extforward_set_addr(srv
, con
, p
, real_remote_addr
) && NULL
!= x_forwarded_proto
) {
527 mod_extforward_set_proto(srv
, con
, CONST_BUF_LEN(x_forwarded_proto
->value
));
530 array_free(forward_array
);
531 return HANDLER_GO_ON
;
534 static int find_end_quoted_string (const char * const s
, int i
) {
537 } while (s
[i
] != '"' && s
[i
] != '\0' && (s
[i
] != '\\' || s
[++i
] != '\0'));
541 static int find_next_semicolon_or_comma_or_eq (const char * const s
, int i
) {
542 for (; s
[i
] != '=' && s
[i
] != ';' && s
[i
] != ',' && s
[i
] != '\0'; ++i
) {
544 i
= find_end_quoted_string(s
, i
);
545 if (s
[i
] == '\0') return -1;
551 static int find_next_semicolon_or_comma (const char * const s
, int i
) {
552 for (; s
[i
] != ';' && s
[i
] != ',' && s
[i
] != '\0'; ++i
) {
554 i
= find_end_quoted_string(s
, i
);
555 if (s
[i
] == '\0') return -1;
561 static int buffer_backslash_unescape (buffer
* const b
) {
562 /* (future: might move to buffer.c) */
564 size_t len
= buffer_string_length(b
);
565 char *p
= memchr(b
->ptr
, '\\', len
);
567 if (NULL
== p
) return 1; /*(nothing to do)*/
569 len
-= (size_t)(p
- b
->ptr
);
570 for (size_t i
= 0; i
< len
; ++i
) {
572 if (++i
== len
) return 0; /*(invalid trailing backslash)*/
576 buffer_string_set_length(b
, (size_t)(p
+j
- b
->ptr
));
580 static handler_t
mod_extforward_Forwarded (server
*srv
, connection
*con
, plugin_data
*p
, buffer
*forwarded
) {
581 /* HTTP list need not consist of param=value tokens,
582 * but this routine expect such for HTTP Forwarded header
583 * Since info in each set of params is only used if from
584 * admin-specified trusted proxy:
585 * - invalid param=value tokens are ignored and skipped
586 * - not checking "for" exists in each set of params
587 * - not checking for duplicated params in each set of params
588 * - not checking canonical form of addr (also might be obfuscated)
589 * - obfuscated tokens permitted in chain, though end of trust is expected
590 * to be non-obfuscated IP for mod_extforward to masquerade as remote IP
591 * future: since (potentially) trusted proxies begin at end of string,
592 * it might be better to parse from end of string rather than parsing from
593 * beginning. Doing so would also allow reducing arbitrary param limit
594 * to number of params permitted per proxy.
596 char * const s
= forwarded
->ptr
;
597 int i
= 0, j
= -1, v
, vlen
, k
, klen
;
598 int used
= (int)buffer_string_length(forwarded
);
599 int ofor
= -1, oproto
, ohost
, oby
, oremote_user
;
600 int offsets
[256];/*(~50 params is more than reasonably expected to handle)*/
602 while (s
[i
] == ' ' || s
[i
] == '\t') ++i
;
603 if (s
[i
] == ';') { ++i
; continue; }
605 if (j
>= (int)(sizeof(offsets
)/sizeof(int))) break;
606 offsets
[++j
] = -1; /*("offset" separating params from next proxy)*/
610 if (s
[i
] == '\0') break;
613 i
= find_next_semicolon_or_comma_or_eq(s
, i
);
615 /*(reject IP spoofing if attacker sets improper quoted-string)*/
616 log_error_write(srv
, __FILE__
, __LINE__
, "s",
617 "invalid quoted-string in Forwarded header");
618 con
->http_status
= 400; /* Bad Request */
620 return HANDLER_FINISHED
;
622 if (s
[i
] != '=') continue;
625 i
= find_next_semicolon_or_comma(s
, i
);
627 /*(reject IP spoofing if attacker sets improper quoted-string)*/
628 log_error_write(srv
, __FILE__
, __LINE__
, "s",
629 "invalid quoted-string in Forwarded header");
630 con
->http_status
= 400; /* Bad Request */
632 return HANDLER_FINISHED
;
634 vlen
= i
- v
; /* might be 0 */
636 /* have k, klen, v, vlen
637 * (might contain quoted string) (contents not validated or decoded)
638 * (might be repeated k)
640 if (0 == klen
) continue; /* invalid k */
641 if (j
>= (int)(sizeof(offsets
)/sizeof(int))-4) break;
649 if (j
>= (int)(sizeof(offsets
)/sizeof(int))-4) {
650 /* error processing Forwarded; too many params; fail closed */
651 log_error_write(srv
, __FILE__
, __LINE__
, "s",
652 "Too many params in Forwarded header");
653 con
->http_status
= 400; /* Bad Request */
655 return HANDLER_FINISHED
;
658 if (-1 == j
) return HANDLER_GO_ON
; /* make no changes */
660 offsets
[used
] = -1; /* mark end of last set of params */
662 while (j
> 0) { /*(param=value pairs, so j > 0, not j >= 0)*/
663 if (-1 == offsets
[j
]) { --j
; continue; }
665 j
-= 3; /*(k, klen, v, vlen come in sets of 4)*/
666 } while ((3 != offsets
[j
+1] /* 3 == sizeof("for")-1 */
667 || 0 != buffer_caseless_compare(s
+offsets
[j
], 3, "for", 3))
668 && 0 != j
-- && -1 != offsets
[j
]);
670 if (-1 == offsets
[j
]) { --j
; continue; }
672 /* remove trailing spaces/tabs and double-quotes from string
673 * (note: not unescaping backslash escapes in quoted string) */
675 vlen
= v
+ offsets
[j
+3];
676 while (vlen
> v
&& (s
[vlen
-1] == ' ' || s
[vlen
-1] == '\t')) --vlen
;
677 if (vlen
> v
+1 && s
[v
] == '"' && s
[vlen
-1] == '"') {
681 /* remove "[]" surrounding IPv6, as well as (optional) port
682 * (assumes properly formatted IPv6 addr from trusted proxy) */
684 do { --vlen
; } while (vlen
> v
&& s
[vlen
] != ']');
686 log_error_write(srv
, __FILE__
, __LINE__
, "s",
687 "Invalid IPv6 addr in Forwarded header");
688 con
->http_status
= 400; /* Bad Request */
690 return HANDLER_FINISHED
;
693 else if (s
[v
] != '_' && s
[v
] != '/' && s
[v
] != 'u') {
694 /* remove (optional) port from non-obfuscated IPv4 */
695 for (klen
=vlen
, vlen
=v
; vlen
< klen
&& s
[vlen
] != ':'; ++vlen
) ;
699 offsets
[j
+3] = vlen
- v
;
701 /* obfuscated ipstr and obfuscated port are also accepted here, as
702 * is path to unix domain socket, but note that backslash escapes
703 * in quoted-string were not unescaped above. Also, if obfuscated
704 * identifiers are rotated by proxies as recommended by RFC, then
705 * maintaining list of trusted identifiers is non-trivial and is not
706 * attempted by this module. */
709 int trusted
= (NULL
!= array_get_element_klen(p
->conf
.forwarder
, s
+v
, vlen
-v
));
711 if (s
[v
] != '_' && s
[v
] != '/'
712 && (7 != (vlen
- v
) || 0 != memcmp(s
+v
, "unknown", 7))) {
713 ofor
= j
; /* save most recent non-obfuscated ipstr */
719 do { --j
; } while (j
> 0 && -1 != offsets
[j
]);
725 /* C funcs getaddrinfo(), inet_addr() require '\0'-terminated IP str */
726 char *ipend
= s
+offsets
[ofor
+2]+offsets
[ofor
+3];
730 rc
= mod_extforward_set_addr(srv
, con
, p
, s
+offsets
[ofor
+2]);
732 if (!rc
) return HANDLER_GO_ON
; /* invalid addr; make no changes */
735 return HANDLER_GO_ON
; /* make no changes */
738 /* parse out params associated with for=<ip> addr set above */
739 oproto
= ohost
= oby
= oremote_user
= -1;
741 if (j
> 0) { do { --j
; } while (j
> 0 && -1 != offsets
[j
]); }
742 if (-1 == offsets
[j
]) ++j
;
743 if (j
== ofor
) j
+= 4;
744 for (; -1 != offsets
[j
]; j
+=4) { /*(k, klen, v, vlen come in sets of 4)*/
745 switch (offsets
[j
+1]) {
748 if (0 == buffer_caseless_compare(s
+offsets
[j
],2,"by",2))
753 /*(already handled above to find IP prior to earliest trusted proxy)*/
755 if (0 == buffer_caseless_compare(s
+offsets
[j
],3,"for",3))
760 if (0 == buffer_caseless_compare(s
+offsets
[j
],4,"host",4))
764 if (0 == buffer_caseless_compare(s
+offsets
[j
],5,"proto",5))
768 if (0 == buffer_caseless_compare(s
+offsets
[j
],11,"remote_user",11))
778 /* remove trailing spaces/tabs, and double-quotes from proto
779 * (note: not unescaping backslash escapes in quoted string) */
780 v
= offsets
[oproto
+2];
781 vlen
= v
+ offsets
[oproto
+3];
782 while (vlen
> v
&& (s
[vlen
-1] == ' ' || s
[vlen
-1] == '\t')) --vlen
;
783 if (vlen
> v
+1 && s
[v
] == '"' && s
[vlen
-1] == '"') { ++v
; --vlen
; }
784 mod_extforward_set_proto(srv
, con
, s
+v
, vlen
-v
);
787 if (p
->conf
.opts
& PROXY_FORWARDED_HOST
) {
789 * - con->request.http_host is not reset in mod_extforward_restore()
790 * but is currently not an issues since con->request.http_host will be
791 * reset by next request. If a new module uses con->request.http_host
792 * in the handle_request_done hook, then should evaluate if that
793 * module should use the forwarded value (probably) or original value.
794 * - due to need to decode and unescape host=..., some extra work is
795 * done in the case where host matches current Host header.
796 * future: might add code to check if Host has actually changed or not
798 * note: change host after mod_extforward_set_proto() since that may
799 * affect scheme port used in http_request_host_policy() host
803 /* find host param set by earliest trusted proxy in proxy chain
804 * (host might be changed anywhere along the chain) */
805 for (j
= i
; j
< used
&& -1 == ohost
; ) {
806 if (-1 == offsets
[j
]) { ++j
; continue; }
807 if (4 == offsets
[j
+1]
808 && 0 == buffer_caseless_compare(s
+offsets
[j
], 4, "host", 4))
810 j
+= 4; /*(k, klen, v, vlen come in sets of 4)*/
813 if (extforward_check_proxy
814 && !buffer_string_is_empty(con
->request
.http_host
)) {
815 array_set_key_value(con
->environment
,
816 CONST_STR_LEN("_L_EXTFORWARD_ACTUAL_HOST"),
817 CONST_BUF_LEN(con
->request
.http_host
));
819 /* remove trailing spaces/tabs, and double-quotes from host */
820 v
= offsets
[ohost
+2];
821 vlen
= v
+ offsets
[ohost
+3];
822 while (vlen
> v
&& (s
[vlen
-1] == ' ' || s
[vlen
-1] == '\t')) --vlen
;
823 if (vlen
> v
+1 && s
[v
] == '"' && s
[vlen
-1] == '"') {
825 buffer_copy_string_len(con
->request
.http_host
, s
+v
, vlen
-v
);
826 if (!buffer_backslash_unescape(con
->request
.http_host
)) {
827 log_error_write(srv
, __FILE__
, __LINE__
, "s",
828 "invalid host= value in Forwarded header");
829 con
->http_status
= 400; /* Bad Request */
831 return HANDLER_FINISHED
;
835 buffer_copy_string_len(con
->request
.http_host
, s
+v
, vlen
-v
);
838 if (0 != http_request_host_policy(con
, con
->request
.http_host
,
840 /*(reject invalid chars in Host)*/
841 log_error_write(srv
, __FILE__
, __LINE__
, "s",
842 "invalid host= value in Forwarded header");
843 con
->http_status
= 400; /* Bad Request */
845 return HANDLER_FINISHED
;
848 config_cond_cache_reset_item(srv
, con
, COMP_HTTP_HOST
);
852 if (p
->conf
.opts
& PROXY_FORWARDED_REMOTE_USER
) {
853 /* find remote_user param set by closest proxy
854 * (auth may have been handled by any trusted proxy in proxy chain) */
855 for (j
= i
; j
< used
; ) {
856 if (-1 == offsets
[j
]) { ++j
; continue; }
857 if (11 == offsets
[j
+1]
858 && 0==buffer_caseless_compare(s
+offsets
[j
],11,"remote_user",11))
860 j
+= 4; /*(k, klen, v, vlen come in sets of 4)*/
862 if (-1 != oremote_user
) {
863 /* ???: should we also support param for auth_type ??? */
864 /* remove trailing spaces/tabs, and double-quotes from remote_user*/
865 v
= offsets
[oremote_user
+2];
866 vlen
= v
+ offsets
[oremote_user
+3];
867 while (vlen
> v
&& (s
[vlen
-1] == ' ' || s
[vlen
-1] == '\t')) --vlen
;
868 if (vlen
> v
+1 && s
[v
] == '"' && s
[vlen
-1] == '"') {
871 array_set_key_value(con
->environment
,
872 CONST_STR_LEN("REMOTE_USER"), s
+v
, vlen
-v
);
873 dsuser
= (data_string
*)
874 array_get_element(con
->environment
, "REMOTE_USER");
875 force_assert(NULL
!= dsuser
);
876 if (!buffer_backslash_unescape(dsuser
->value
)) {
877 log_error_write(srv
, __FILE__
, __LINE__
, "s",
878 "invalid remote_user= value in Forwarded header");
879 con
->http_status
= 400; /* Bad Request */
881 return HANDLER_FINISHED
;
885 array_set_key_value(con
->environment
,
886 CONST_STR_LEN("REMOTE_USER"), s
+v
, vlen
-v
);
892 if ((p
->conf
.opts
& PROXY_FORWARDED_CREATE_XFF
)
893 && NULL
== array_get_element(con
->request
.headers
, "X-Forwarded-For")) {
894 /* create X-Forwarded-For if not present
895 * (and at least original connecting IP is a trusted proxy) */
897 data_string
*dsxff
= (data_string
*)
898 array_get_unused_element(con
->request
.headers
, TYPE_STRING
);
899 if (NULL
== dsxff
) dsxff
= data_string_init();
900 buffer_copy_string_len(dsxff
->key
, CONST_STR_LEN("X-Forwarded-For"));
901 array_insert_unique(con
->request
.headers
, (data_unset
*)dsxff
);
903 for (j
= 0; j
< used
; ) {
904 if (-1 == offsets
[j
]) { ++j
; continue; }
905 if (3 == offsets
[j
+1]
906 && 0 == buffer_caseless_compare(s
+offsets
[j
], 3, "for", 3)) {
907 if (!buffer_string_is_empty(xff
))
908 buffer_append_string_len(xff
, CONST_STR_LEN(", "));
909 /* quoted-string, IPv6 brackets, and :port already removed */
912 buffer_append_string_len(xff
, s
+v
, vlen
);
913 if (s
[v
-1] != '=') { /*(must have been quoted-string)*/
915 memchr(xff
->ptr
+buffer_string_length(xff
)-vlen
,'\\',vlen
);
916 if (NULL
!= x
) { /* backslash unescape in-place */
917 for (v
= 0; x
[v
]; ++x
) {
918 if (x
[v
] == '\\' && x
[++v
] == '\0')
919 break; /*(invalid trailing backslash)*/
922 buffer_string_set_length(xff
, x
- xff
->ptr
);
925 /* skip to next group; take first "for=..." in group
926 * (should be 0 or 1 "for=..." per group, but not trusted) */
927 do { j
+= 4; } while (-1 != offsets
[j
]);
931 j
+= 4; /*(k, klen, v, vlen come in sets of 4)*/
936 return HANDLER_GO_ON
;
939 URIHANDLER_FUNC(mod_extforward_uri_handler
) {
940 plugin_data
*p
= p_d
;
941 data_string
*forwarded
= NULL
;
942 handler_ctx
*hctx
= con
->plugin_ctx
[p
->id
];
944 mod_extforward_patch_connection(srv
, con
, p
);
946 if (con
->conf
.log_request_handling
) {
947 log_error_write(srv
, __FILE__
, __LINE__
, "s",
948 "-- mod_extforward_uri_handler called");
951 if (p
->conf
.hap_PROXY_ssl_client_verify
) {
953 if (NULL
!= hctx
&& hctx
->ssl_client_verify
&& NULL
!= hctx
->env
954 && NULL
!= (ds
= (data_string
*)array_get_element(hctx
->env
, "SSL_CLIENT_S_DN_CN"))) {
955 array_set_key_value(con
->environment
,
956 CONST_STR_LEN("SSL_CLIENT_VERIFY"),
957 CONST_STR_LEN("SUCCESS"));
958 array_set_key_value(con
->environment
,
959 CONST_STR_LEN("REMOTE_USER"),
960 CONST_BUF_LEN(ds
->value
));
961 array_set_key_value(con
->environment
,
962 CONST_STR_LEN("AUTH_TYPE"),
963 CONST_STR_LEN("SSL_CLIENT_VERIFY"));
965 array_set_key_value(con
->environment
,
966 CONST_STR_LEN("SSL_CLIENT_VERIFY"),
967 CONST_STR_LEN("NONE"));
971 for (size_t k
= 0; k
< p
->conf
.headers
->used
&& NULL
== forwarded
; ++k
) {
972 forwarded
= (data_string
*) array_get_element_klen(con
->request
.headers
, CONST_BUF_LEN(((data_string
*)p
->conf
.headers
->data
[k
])->value
));
974 if (NULL
== forwarded
) {
975 if (con
->conf
.log_request_handling
) {
976 log_error_write(srv
, __FILE__
, __LINE__
, "s", "no forward header found, skipping");
979 return HANDLER_GO_ON
;
982 /* if the remote ip itself is not trusted, then do nothing */
983 if (IP_UNTRUSTED
== is_proxy_trusted(con
->dst_addr_buf
, p
)) {
984 if (con
->conf
.log_request_handling
) {
985 log_error_write(srv
, __FILE__
, __LINE__
, "sbs",
986 "remote address", con
->dst_addr_buf
, "is NOT a trusted proxy, skipping");
989 return HANDLER_GO_ON
;
992 if (buffer_is_equal_caseless_string(forwarded
->key
, CONST_STR_LEN("Forwarded"))) {
993 return mod_extforward_Forwarded(srv
, con
, p
, forwarded
->value
);
996 return mod_extforward_X_Forwarded_For(srv
, con
, p
, forwarded
->value
);
1000 CONNECTION_FUNC(mod_extforward_handle_request_env
) {
1001 plugin_data
*p
= p_d
;
1002 handler_ctx
*hctx
= con
->plugin_ctx
[p
->id
];
1004 if (NULL
== hctx
|| NULL
== hctx
->env
) return HANDLER_GO_ON
;
1005 for (size_t i
=0; i
< hctx
->env
->used
; ++i
) {
1006 /* note: replaces values which may have been set by mod_openssl
1007 * (when mod_extforward is listed after mod_openssl in server.modules)*/
1008 data_string
*ds
= (data_string
*)hctx
->env
->data
[i
];
1009 array_set_key_value(con
->environment
,
1010 CONST_BUF_LEN(ds
->key
), CONST_BUF_LEN(ds
->value
));
1012 return HANDLER_GO_ON
;
1016 CONNECTION_FUNC(mod_extforward_restore
) {
1017 plugin_data
*p
= p_d
;
1018 handler_ctx
*hctx
= con
->plugin_ctx
[p
->id
];
1020 if (!hctx
) return HANDLER_GO_ON
;
1022 if (NULL
!= hctx
->saved_network_read
) {
1023 con
->network_read
= hctx
->saved_network_read
;
1024 hctx
->saved_network_read
= NULL
;
1027 if (NULL
!= hctx
->saved_remote_addr_buf
) {
1028 con
->dst_addr
= hctx
->saved_remote_addr
;
1029 buffer_free(con
->dst_addr_buf
);
1030 con
->dst_addr_buf
= hctx
->saved_remote_addr_buf
;
1031 hctx
->saved_remote_addr_buf
= NULL
;
1032 /* Now, clean the conf_cond cache, because we may have changed the results of tests */
1033 config_cond_cache_reset_item(srv
, con
, COMP_HTTP_REMOTE_IP
);
1036 if (NULL
== hctx
->env
) {
1037 handler_ctx_free(hctx
);
1038 con
->plugin_ctx
[p
->id
] = NULL
;
1041 return HANDLER_GO_ON
;
1045 CONNECTION_FUNC(mod_extforward_handle_con_close
)
1047 plugin_data
*p
= p_d
;
1048 handler_ctx
*hctx
= con
->plugin_ctx
[p
->id
];
1051 if (NULL
!= hctx
->saved_network_read
) {
1052 con
->network_read
= hctx
->saved_network_read
;
1054 if (NULL
!= hctx
->saved_remote_addr_buf
) {
1055 con
->dst_addr
= hctx
->saved_remote_addr
;
1056 buffer_free(con
->dst_addr_buf
);
1057 con
->dst_addr_buf
= hctx
->saved_remote_addr_buf
;
1059 if (NULL
!= hctx
->env
) {
1060 array_free(hctx
->env
);
1062 handler_ctx_free(hctx
);
1063 con
->plugin_ctx
[p
->id
] = NULL
;
1066 return HANDLER_GO_ON
;
1070 static int mod_extforward_network_read (server
*srv
, connection
*con
, chunkqueue
*cq
, off_t max_bytes
);
1072 CONNECTION_FUNC(mod_extforward_handle_con_accept
)
1074 plugin_data
*p
= p_d
;
1075 mod_extforward_patch_connection(srv
, con
, p
);
1076 if (!p
->conf
.hap_PROXY
) return HANDLER_GO_ON
;
1077 if (IP_TRUSTED
== is_proxy_trusted(con
->dst_addr_buf
, p
)) {
1078 handler_ctx
*hctx
= handler_ctx_init();
1079 con
->plugin_ctx
[p
->id
] = hctx
;
1080 hctx
->saved_network_read
= con
->network_read
;
1081 con
->network_read
= mod_extforward_network_read
;
1084 if (con
->conf
.log_request_handling
) {
1085 log_error_write(srv
, __FILE__
, __LINE__
, "sbs",
1086 "remote address", con
->dst_addr_buf
,
1087 "is NOT a trusted proxy, skipping");
1090 return HANDLER_GO_ON
;
1094 /* this function is called at dlopen() time and inits the callbacks */
1096 int mod_extforward_plugin_init(plugin
*p
);
1097 int mod_extforward_plugin_init(plugin
*p
) {
1098 p
->version
= LIGHTTPD_VERSION_ID
;
1099 p
->name
= buffer_init_string("extforward");
1101 p
->init
= mod_extforward_init
;
1102 p
->handle_connection_accept
= mod_extforward_handle_con_accept
;
1103 p
->handle_uri_raw
= mod_extforward_uri_handler
;
1104 p
->handle_request_env
= mod_extforward_handle_request_env
;
1105 p
->handle_request_done
= mod_extforward_restore
;
1106 p
->connection_reset
= mod_extforward_restore
;
1107 p
->handle_connection_close
= mod_extforward_handle_con_close
;
1108 p
->set_defaults
= mod_extforward_set_defaults
;
1109 p
->cleanup
= mod_extforward_free
;
1120 * http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
1124 The code below is an example of how a receiver may deal with both versions of
1125 the protocol header for TCP over IPv4 or IPv6. The function is supposed to be
1126 called upon a read event. Addresses may be directly copied into their final
1127 memory location since they're transported in network byte order. The sending
1128 side is even simpler and can easily be deduced from this sample code.
1132 union hap_PROXY_hdr
{
1142 struct { /* for TCP/UDP over IPv4, len = 12 */
1148 struct { /* for TCP/UDP over IPv6, len = 36 */
1149 uint8_t src_addr
[16];
1150 uint8_t dst_addr
[16];
1154 struct { /* for AF_UNIX sockets, len = 216 */
1155 uint8_t src_addr
[108];
1156 uint8_t dst_addr
[108];
1163 If the length specified in the PROXY protocol header indicates that additional
1164 bytes are part of the header beyond the address information, a receiver may
1165 choose to skip over and ignore those bytes, or attempt to interpret those
1168 The information in those bytes will be arranged in Type-Length-Value (TLV
1169 vectors) in the following format. The first byte is the Type of the vector.
1170 The second two bytes represent the length in bytes of the value (not included
1171 the Type and Length bytes), and following the length field is the number of
1172 bytes specified by the length.
1178 /*uint8_t value[0];*//* C99 zero-length array */
1182 The following types have already been registered for the <type> field :
1185 #define PP2_TYPE_ALPN 0x01
1186 #define PP2_TYPE_AUTHORITY 0x02
1187 #define PP2_TYPE_CRC32C 0x03
1188 #define PP2_TYPE_NOOP 0x04
1189 #define PP2_TYPE_SSL 0x20
1190 #define PP2_SUBTYPE_SSL_VERSION 0x21
1191 #define PP2_SUBTYPE_SSL_CN 0x22
1192 #define PP2_SUBTYPE_SSL_CIPHER 0x23
1193 #define PP2_SUBTYPE_SSL_SIG_ALG 0x24
1194 #define PP2_SUBTYPE_SSL_KEY_ALG 0x25
1195 #define PP2_TYPE_NETNS 0x30
1198 For the type PP2_TYPE_SSL, the value is itselv a defined like this :
1201 struct pp2_tlv_ssl
{
1204 /*struct pp2_tlv sub_tlv[0];*//* C99 zero-length array */
1208 And the <client> field is made of a bit field from the following values,
1209 indicating which element is present :
1212 #define PP2_CLIENT_SSL 0x01
1213 #define PP2_CLIENT_CERT_CONN 0x02
1214 #define PP2_CLIENT_CERT_SESS 0x04
1219 #ifndef MSG_DONTWAIT
1220 #define MSG_DONTWAIT 0
1222 #ifndef MSG_NOSIGNAL
1223 #define MSG_NOSIGNAL 0
1226 /* returns 0 if needs to poll, <0 upon error or >0 is protocol vers (success) */
1227 static int hap_PROXY_recv (const int fd
, union hap_PROXY_hdr
* const hdr
)
1229 static const char v2sig
[12] =
1230 "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A";
1237 ret
= recv(fd
, hdr
, sizeof(*hdr
), MSG_PEEK
|MSG_DONTWAIT
|MSG_NOSIGNAL
);
1238 } while (-1 == ret
&& errno
== EINTR
);
1241 return (errno
== EAGAIN
1243 #if EAGAIN != EWOULDBLOCK
1244 || errno
== EWOULDBLOCK
1249 if (ret
>= 16 && 0 == memcmp(&hdr
->v2
, v2sig
, 12)
1250 && (hdr
->v2
.ver_cmd
& 0xF0) == 0x20) {
1252 sz
= 16 + (size_t)ntohs(hdr
->v2
.len
);
1253 if ((size_t)ret
< sz
)
1254 return -2; /* truncated or too large header */
1256 switch (hdr
->v2
.ver_cmd
& 0xF) {
1257 case 0x01: break; /* PROXY command */
1258 case 0x00: break; /* LOCAL command */
1259 default: return -2; /* not a supported command */
1262 else if (ret
>= 8 && 0 == memcmp(hdr
->v1
.line
, "PROXY", 5)) {
1263 const char *end
= memchr(hdr
->v1
.line
, '\r', ret
- 1);
1264 if (!end
|| end
[1] != '\n')
1265 return -2; /* partial or invalid header */
1267 sz
= (size_t)(end
+ 2 - hdr
->v1
.line
); /* skip header + CRLF */
1270 /* Wrong protocol */
1274 /* we need to consume the appropriate amount of data from the socket
1275 * (overwrites existing contents of hdr with same data) */
1277 ret
= recv(fd
, hdr
, sz
, MSG_DONTWAIT
|MSG_NOSIGNAL
);
1278 } while (-1 == ret
&& errno
== EINTR
);
1279 if (ret
< 0) return -1;
1280 if (1 == ver
) hdr
->v1
.line
[sz
-2] = '\0'; /*terminate str to ease parsing*/
1285 static int mod_extforward_hap_PROXY_v1 (connection
* const con
,
1286 union hap_PROXY_hdr
* const hdr
)
1289 __coverity_tainted_data_sink__(hdr
);
1293 * "PROXY TCP4 255.255.255.255 255.255.255.255 65535 65535\r\n"
1294 * "PROXY TCP6 ffff:f...f:ffff ffff:f...f:ffff 65535 65535\r\n"
1295 * "PROXY UNKNOWN\r\n"
1296 * "PROXY UNKNOWN ffff:f...f:ffff ffff:f...f:ffff 65535 65535\r\n"
1298 char *s
= hdr
->v1
.line
+ sizeof("PROXY")-1; /*checked in hap_PROXY_recv()*/
1299 char *src_addr
, *dst_addr
, *src_port
, *dst_port
, *e
;
1301 long src_lport
, dst_lport
;
1302 if (*s
!= ' ') return -1;
1304 if (s
[0] == 'T' && s
[1] == 'C' && s
[2] == 'P' && s
[4] == ' ') {
1307 } else if (s
[3] == '6') {
1315 else if (0 == memcmp(s
, "UNKNOWN", sizeof("UNKNOWN")-1)
1316 && (s
[7] == '\0' || s
[7] == ' ')) {
1317 return 0; /* keep local connection address */
1323 /*(strsep() should be fairly portable, but is not standard)*/
1325 dst_addr
= strchr(src_addr
, ' ');
1326 if (NULL
== dst_addr
) return -1;
1328 src_port
= strchr(dst_addr
, ' ');
1329 if (NULL
== src_port
) return -1;
1331 dst_port
= strchr(src_port
, ' ');
1332 if (NULL
== dst_port
) return -1;
1335 src_lport
= strtol(src_port
, &e
, 10);
1336 if (src_lport
<= 0 || src_lport
> USHRT_MAX
|| *e
!= '\0') return -1;
1337 dst_lport
= strtol(dst_port
, &e
, 10);
1338 if (dst_lport
<= 0 || dst_lport
> USHRT_MAX
|| *e
!= '\0') return -1;
1340 if (1 != sock_addr_inet_pton(&con
->dst_addr
,
1341 src_addr
, family
, (unsigned short)src_lport
))
1343 /* Forwarded by=... could be saved here.
1344 * (see additional comments in mod_extforward_hap_PROXY_v2()) */
1346 /* re-parse addr to string to normalize
1347 * (instead of trusting PROXY to provide canonicalized src_addr string)
1348 * (should prefer PROXY v2 protocol if concerned about performance) */
1349 sock_addr_inet_ntop_copy_buffer(con
->dst_addr_buf
, &con
->dst_addr
);
1355 static int mod_extforward_hap_PROXY_v2 (connection
* const con
,
1356 union hap_PROXY_hdr
* const hdr
)
1359 __coverity_tainted_data_sink__(hdr
);
1362 /* If HAProxy-PROXY protocol used, then lighttpd acts as transparent proxy,
1363 * masquerading as servicing the client IP provided in by HAProxy-PROXY hdr.
1364 * The connecting con->dst_addr and con->dst_addr_buf are not saved here,
1365 * so that info is lost unless getsockname() and getpeername() are used.
1366 * One result is that mod_proxy will use the masqueraded IP instead of the
1367 * actual IP when updated Forwarded and X-Forwarded-For (but if actual
1368 * connection IPs needed, better to save the info here rather than use
1369 * syscalls to retrieve the info later).
1370 * (Exception: con->dst_addr can be further changed if mod_extforward parses
1371 * Forwaded or X-Forwarded-For request headers later, after request headers
1372 * have been received.)
1375 /* Forwarded by=... could be saved here. The by param is for backends to be
1376 * able to construct URIs for that interface (interface on server which
1377 * received request and made PROXY connection here), though that server
1378 * should provide that information in updated Forwarded or X-Forwarded-For
1380 /*struct sockaddr_storage by;*/
1382 /* Addresses provided by HAProxy-PROXY protocol are in network byte order.
1383 * Note: addr info is not validated, so do not accept HAProxy-PROXY
1384 * protocol from untrusted servers. For example, untrusted servers from
1385 * which HAProxy-PROXY protocol is accepted (don't do that) could pretend
1386 * to be from the internal network and might thereby bypass security policy.
1389 /* (Clear con->dst_addr with memset() in case actual and proxies IPs
1390 * are different domains, e.g. one is IPv4 and the other is IPv6) */
1392 struct pp2_tlv
*tlv
;
1393 uint32_t sz
= ntohs(hdr
->v2
.len
);
1396 switch (hdr
->v2
.ver_cmd
& 0xF) {
1397 case 0x01: break; /* PROXY command */
1398 case 0x00: return 0;/* LOCAL command; keep local connection address */
1399 default: return -1;/* should not happen; validated in hap_PROXY_recv()*/
1404 switch (hdr
->v2
.fam
) {
1405 case 0x11: /* TCPv4 */
1406 memset(&con
->dst_addr
.ipv4
, 0, sizeof(struct sockaddr_in
));
1407 con
->dst_addr
.ipv4
.sin_family
= AF_INET
;
1408 con
->dst_addr
.ipv4
.sin_port
= hdr
->v2
.addr
.ip4
.src_port
;
1409 con
->dst_addr
.ipv4
.sin_addr
.s_addr
= hdr
->v2
.addr
.ip4
.src_addr
;
1410 sock_addr_inet_ntop_copy_buffer(con
->dst_addr_buf
, &con
->dst_addr
);
1412 ((struct sockaddr_in
*)&by
)->sin_family
= AF_INET
;
1413 ((struct sockaddr_in
*)&by
)->sin_addr
.s_addr
=
1414 hdr
->v2
.addr
.ip4
.dst_addr
;
1415 ((struct sockaddr_in
*)&by
)->sin_port
=
1416 hdr
->v2
.addr
.ip4
.dst_port
;
1418 len
= (uint32_t)sizeof(hdr
->v2
.addr
.ip4
);
1421 case 0x21: /* TCPv6 */
1422 memset(&con
->dst_addr
.ipv6
, 0, sizeof(struct sockaddr_in6
));
1423 con
->dst_addr
.ipv6
.sin6_family
= AF_INET6
;
1424 con
->dst_addr
.ipv6
.sin6_port
= hdr
->v2
.addr
.ip6
.src_port
;
1425 memcpy(&con
->dst_addr
.ipv6
.sin6_addr
, hdr
->v2
.addr
.ip6
.src_addr
, 16);
1426 sock_addr_inet_ntop_copy_buffer(con
->dst_addr_buf
, &con
->dst_addr
);
1428 ((struct sockaddr_in6
*)&by
)->sin6_family
= AF_INET6
;
1429 memcpy(&((struct sockaddr_in6
*)&by
)->sin6_addr
,
1430 hdr
->v2
.addr
.ip6
.dst_addr
, 16);
1431 ((struct sockaddr_in6
*)&by
)->sin6_port
=
1432 hdr
->v2
.addr
.ip6
.dst_port
;
1434 len
= (uint32_t)sizeof(hdr
->v2
.addr
.ip6
);
1437 #ifdef HAVE_SYS_UN_H
1438 case 0x31: /* UNIX domain socket */
1440 char *src_addr
= (char *)hdr
->v2
.addr
.unx
.src_addr
;
1441 char *z
= memchr(src_addr
, '\0', UNIX_PATH_MAX
);
1442 if (NULL
== z
) return -1; /* invalid addr; too long */
1443 len
= (uint32_t)(z
- src_addr
+ 1); /*(+1 for '\0')*/
1444 memset(&con
->dst_addr
.un
, 0, sizeof(struct sockaddr_un
));
1445 con
->dst_addr
.un
.sun_family
= AF_UNIX
;
1446 memcpy(&con
->dst_addr
.un
.sun_path
, src_addr
, len
);
1447 buffer_copy_string_len(con
->dst_addr_buf
, src_addr
, len
);
1449 #if 0 /*(dst_addr should be identical to src_addr for AF_UNIX)*/
1450 ((struct sockaddr_un
*)&by
)->sun_family
= AF_UNIX
;
1451 memcpy(&((struct sockaddr_un
*)&by
)->sun_path
,
1452 hdr
->v2
.addr
.unx
.dst_addr
, 108);
1454 len
= (uint32_t)sizeof(hdr
->v2
.addr
.unx
);
1457 default: /* keep local connection address; unsupported protocol */
1461 /* (optional) Type-Length-Value (TLV vectors) follow addresses */
1463 tlv
= (struct pp2_tlv
*)((char *)hdr
+ 16);
1464 for (sz
-= len
, len
-= 3; sz
>= 3; sz
-= 3 + len
) {
1465 tlv
= (struct pp2_tlv
*)((char *)tlv
+ 3 + len
);
1466 len
= ((uint32_t)tlv
->length_hi
<< 8) | tlv
->length_lo
;
1467 if (3 + len
> sz
) break; /*(invalid TLV)*/
1468 switch (tlv
->type
) {
1469 #if 0 /*(not implemented here)*/
1471 case PP2_TYPE_AUTHORITY
:
1472 case PP2_TYPE_CRC32C
:
1474 case PP2_TYPE_SSL
: {
1475 static const uint32_t zero
= 0;
1477 con
->plugin_ctx
[mod_extforward_plugin_data_singleton
->id
];
1478 struct pp2_tlv_ssl
*tlv_ssl
=
1479 (struct pp2_tlv_ssl
*)(void *)((char *)tlv
+3);
1480 struct pp2_tlv
*subtlv
= tlv
;
1481 if (tlv_ssl
->client
& PP2_CLIENT_SSL
) {
1482 buffer_copy_string_len(con
->proto
, CONST_STR_LEN("https"));
1484 if ((tlv_ssl
->client
& (PP2_CLIENT_CERT_CONN
|PP2_CLIENT_CERT_SESS
))
1485 && 0 == memcmp(&tlv_ssl
->verify
, &zero
, 4)) { /* misaligned */
1486 hctx
->ssl_client_verify
= 1;
1488 for (uint32_t subsz
= len
-5, n
= 5; subsz
>= 3; subsz
-= 3 + n
) {
1489 subtlv
= (struct pp2_tlv
*)((char *)subtlv
+ 3 + n
);
1490 n
= ((uint32_t)subtlv
->length_hi
<< 8) | subtlv
->length_lo
;
1491 if (3 + n
> subsz
) break; /*(invalid TLV)*/
1492 if (NULL
== hctx
->env
) hctx
->env
= array_init();
1493 switch (subtlv
->type
) {
1494 case PP2_SUBTYPE_SSL_VERSION
:
1495 array_set_key_value(hctx
->env
,
1496 CONST_STR_LEN("SSL_PROTOCOL"),
1497 (char *)subtlv
+3, n
);
1499 case PP2_SUBTYPE_SSL_CN
:
1500 /* (tlv_ssl->client & PP2_CLIENT_CERT_CONN)
1502 * (tlv_ssl->client & PP2_CLIENT_CERT_SESS) */
1503 array_set_key_value(hctx
->env
,
1504 CONST_STR_LEN("SSL_CLIENT_S_DN_CN"),
1505 (char *)subtlv
+3, n
);
1507 case PP2_SUBTYPE_SSL_CIPHER
:
1508 array_set_key_value(hctx
->env
,
1509 CONST_STR_LEN("SSL_CIPHER"),
1510 (char *)subtlv
+3, n
);
1512 case PP2_SUBTYPE_SSL_SIG_ALG
:
1513 array_set_key_value(hctx
->env
,
1514 CONST_STR_LEN("SSL_SERVER_A_SIG"),
1515 (char *)subtlv
+3, n
);
1517 case PP2_SUBTYPE_SSL_KEY_ALG
:
1518 array_set_key_value(hctx
->env
,
1519 CONST_STR_LEN("SSL_SERVER_A_KEY"),
1520 (char *)subtlv
+3, n
);
1528 #if 0 /*(not implemented here)*/
1529 case PP2_TYPE_NETNS
:
1531 /*case PP2_TYPE_NOOP:*//* no-op */
1541 static int mod_extforward_network_read (server
*srv
, connection
*con
,
1542 chunkqueue
*cq
, off_t max_bytes
)
1544 /* XXX: when using hap-PROXY protocol, currently avoid overhead of setting
1545 * _L_ environment variables for mod_proxy to accurately set Forwarded hdr
1546 * In the future, might add config switch to enable doing this extra work */
1548 union hap_PROXY_hdr hdr
;
1549 int rc
= hap_PROXY_recv(con
->fd
, &hdr
);
1551 case 2: rc
= mod_extforward_hap_PROXY_v2(con
, &hdr
); break;
1552 case 1: rc
= mod_extforward_hap_PROXY_v1(con
, &hdr
); break;
1553 case 0: return 0; /*(errno == EAGAIN || errno == EWOULDBLOCK)*/
1554 case -1: log_error_write(srv
, __FILE__
, __LINE__
, "ss",
1555 "hap-PROXY recv()", strerror(errno
));
1557 case -2: log_error_write(srv
, __FILE__
, __LINE__
, "s",
1558 "hap-PROXY proto received "
1559 "invalid/unsupported request");
1561 default: rc
= -1; break;
1564 mod_extforward_restore(srv
, con
, mod_extforward_plugin_data_singleton
);
1565 return (0 == rc
) ? con
->network_read(srv
, con
, cq
, max_bytes
) : rc
;