[mod_extforward] quiet clang compiler warning
[lighttpd.git] / src / mod_extforward.c
blobb75392889bc21e92fc9c7e8c35aa91d05d1ccf87
1 #include "first.h"
3 #include "base.h"
4 #include "log.h"
5 #include "buffer.h"
6 #include "request.h"
7 #include "inet_ntop_cache.h"
9 #include "plugin.h"
11 #include "configfile.h"
13 #include <stdlib.h>
14 #include <string.h>
15 #include <errno.h>
17 #include "sys-socket.h"
18 #ifndef _WIN32
19 #include <netdb.h>
20 #endif
22 /**
23 * mod_extforward.c for lighttpd, by comman.kang <at> gmail <dot> com
24 * extended, modified by Lionel Elie Mamane (LEM), lionel <at> mamane <dot> lu
25 * support chained proxies by glen@delfi.ee, #1528
27 * Config example:
29 * Trust proxy 10.0.0.232 and 10.0.0.232
30 * extforward.forwarder = ( "10.0.0.232" => "trust",
31 * "10.0.0.233" => "trust" )
33 * Trust all proxies (NOT RECOMMENDED!)
34 * extforward.forwarder = ( "all" => "trust")
36 * Note that "all" has precedence over specific entries,
37 * so "all except" setups will not work.
39 * In case you have chained proxies, you can add all their IP's to the
40 * config. However "all" has effect only on connecting IP, as the
41 * X-Forwarded-For header can not be trusted.
43 * Note: The effect of this module is variable on $HTTP["remotip"] directives and
44 * other module's remote ip dependent actions.
45 * Things done by modules before we change the remoteip or after we reset it will match on the proxy's IP.
46 * Things done in between these two moments will match on the real client's IP.
47 * The moment things are done by a module depends on in which hook it does things and within the same hook
48 * on whether they are before/after us in the module loading order
49 * (order in the server.modules directive in the config file).
51 * Tested behaviours:
53 * mod_access: Will match on the real client.
55 * mod_accesslog:
56 * In order to see the "real" ip address in access log ,
57 * you'll have to load mod_extforward after mod_accesslog.
58 * like this:
60 * server.modules = (
61 * .....
62 * mod_accesslog,
63 * mod_extforward
64 * )
68 /* plugin config for all request/connections */
70 typedef enum {
71 PROXY_FORWARDED_NONE = 0x00,
72 PROXY_FORWARDED_FOR = 0x01,
73 PROXY_FORWARDED_PROTO = 0x02,
74 PROXY_FORWARDED_HOST = 0x04,
75 PROXY_FORWARDED_BY = 0x08,
76 PROXY_FORWARDED_REMOTE_USER = 0x10
77 } proxy_forwarded_t;
79 typedef struct {
80 array *forwarder;
81 array *headers;
82 array *opts_params;
83 unsigned int opts;
84 unsigned int hap_PROXY;
85 } plugin_config;
87 typedef struct {
88 PLUGIN_DATA;
90 plugin_config **config_storage;
92 plugin_config conf;
93 } plugin_data;
95 static plugin_data *mod_extforward_plugin_data_singleton;
96 static int extforward_check_proxy;
99 /* context , used for restore remote ip */
101 typedef struct {
102 /* per-request state */
103 sock_addr saved_remote_addr;
104 buffer *saved_remote_addr_buf;
106 /* hap-PROXY protocol prior to receiving first request */
107 int(*saved_network_read)(server *, connection *, chunkqueue *, off_t);
109 /* connection-level state applied to requests in handle_request_env */
110 array *env;
111 int ssl_client_verify;
112 } handler_ctx;
115 static handler_ctx * handler_ctx_init(void) {
116 handler_ctx * hctx;
117 hctx = calloc(1, sizeof(*hctx));
118 return hctx;
121 static void handler_ctx_free(handler_ctx *hctx) {
122 free(hctx);
125 /* init the plugin data */
126 INIT_FUNC(mod_extforward_init) {
127 plugin_data *p;
128 p = calloc(1, sizeof(*p));
129 mod_extforward_plugin_data_singleton = p;
130 return p;
133 /* destroy the plugin data */
134 FREE_FUNC(mod_extforward_free) {
135 plugin_data *p = p_d;
137 UNUSED(srv);
139 if (!p) return HANDLER_GO_ON;
141 if (p->config_storage) {
142 size_t i;
144 for (i = 0; i < srv->config_context->used; i++) {
145 plugin_config *s = p->config_storage[i];
147 if (NULL == s) continue;
149 array_free(s->forwarder);
150 array_free(s->headers);
151 array_free(s->opts_params);
153 free(s);
155 free(p->config_storage);
159 free(p);
161 return HANDLER_GO_ON;
164 /* handle plugin config and check values */
166 SETDEFAULTS_FUNC(mod_extforward_set_defaults) {
167 plugin_data *p = p_d;
168 size_t i = 0;
170 config_values_t cv[] = {
171 { "extforward.forwarder", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
172 { "extforward.headers", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
173 { "extforward.params", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
174 { "extforward.hap-PROXY", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 3 */
175 { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
178 if (!p) return HANDLER_ERROR;
180 p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config *));
182 for (i = 0; i < srv->config_context->used; i++) {
183 data_config const* config = (data_config const*)srv->config_context->data[i];
184 plugin_config *s;
186 s = calloc(1, sizeof(plugin_config));
187 s->forwarder = array_init();
188 s->headers = array_init();
189 s->opts_params = array_init();
190 s->opts = PROXY_FORWARDED_NONE;
192 cv[0].destination = s->forwarder;
193 cv[1].destination = s->headers;
194 cv[2].destination = s->opts_params;
195 cv[3].destination = &s->hap_PROXY;
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"))) {
217 data_string *ds;
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;
239 } else
240 #endif
241 if (buffer_is_equal_string(du->key, CONST_STR_LEN("host"))) {
242 param = PROXY_FORWARDED_HOST;
243 #if 0
244 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("proto"))) {
245 param = PROXY_FORWARDED_PROTO;
246 #endif
247 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("remote_user"))) {
248 param = PROXY_FORWARDED_REMOTE_USER;
249 } else {
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"))) {
257 s->opts |= param;
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;
266 } else {
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 to 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];
283 if (s->hap_PROXY) {
284 size_t j;
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"))) {
288 break;
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\"");
296 break;
299 break;
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;
307 break;
311 return HANDLER_GO_ON;
314 #define PATCH(x) \
315 p->conf.x = s->x;
316 static int mod_extforward_patch_connection(server *srv, connection *con, plugin_data *p) {
317 size_t i, j;
318 plugin_config *s = p->config_storage[0];
320 PATCH(forwarder);
321 PATCH(headers);
322 PATCH(opts);
323 PATCH(hap_PROXY);
325 /* skip the first, the global context */
326 for (i = 1; i < srv->config_context->used; i++) {
327 data_config *dc = (data_config *)srv->config_context->data[i];
328 s = p->config_storage[i];
330 /* condition didn't match */
331 if (!config_check_cond(srv, con, dc)) continue;
333 /* merge config */
334 for (j = 0; j < dc->value->used; j++) {
335 data_unset *du = dc->value->data[j];
337 if (buffer_is_equal_string(du->key, CONST_STR_LEN("extforward.forwarder"))) {
338 PATCH(forwarder);
339 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("extforward.headers"))) {
340 PATCH(headers);
341 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("extforward.params"))) {
342 PATCH(opts);
343 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("extforward.hap-PROXY"))) {
344 PATCH(hap_PROXY);
349 return 0;
351 #undef PATCH
354 static void put_string_into_array_len(array *ary, const char *str, int len)
356 data_string *tempdata;
357 if (len == 0)
358 return;
359 tempdata = data_string_init();
360 buffer_copy_string_len(tempdata->value,str,len);
361 array_insert_unique(ary,(data_unset *)tempdata);
364 extract a forward array from the environment
366 static array *extract_forward_array(buffer *pbuffer)
368 array *result = array_init();
369 if (!buffer_string_is_empty(pbuffer)) {
370 char *base, *curr;
371 /* state variable, 0 means not in string, 1 means in string */
372 int in_str = 0;
373 for (base = pbuffer->ptr, curr = pbuffer->ptr; *curr; curr++) {
374 if (in_str) {
375 if ((*curr > '9' || *curr < '0') && *curr != '.' && *curr != ':' && (*curr < 'a' || *curr > 'f') && (*curr < 'A' || *curr > 'F')) {
376 /* found an separator , insert value into result array */
377 put_string_into_array_len(result, base, curr - base);
378 /* change state to not in string */
379 in_str = 0;
381 } else {
382 if ((*curr >= '0' && *curr <= '9') || *curr == ':' || (*curr >= 'a' && *curr <= 'f') || (*curr >= 'A' && *curr <= 'F')) {
383 /* found leading char of an IP address, move base pointer and change state */
384 base = curr;
385 in_str = 1;
389 /* if breaking out while in str, we got to the end of string, so add it */
390 if (in_str) {
391 put_string_into_array_len(result, base, curr - base);
394 return result;
397 #define IP_TRUSTED 1
398 #define IP_UNTRUSTED 0
400 * check whether ip is trusted, return 1 for trusted , 0 for untrusted
402 static int is_proxy_trusted(const buffer *ipstr, plugin_data *p)
404 data_string* allds = (data_string *)array_get_element(p->conf.forwarder, "all");
406 if (allds) {
407 if (strcasecmp(allds->value->ptr, "trust") == 0) {
408 return IP_TRUSTED;
409 } else {
410 return IP_UNTRUSTED;
414 return (data_string *)array_get_element_klen(p->conf.forwarder, CONST_BUF_LEN(ipstr)) ? IP_TRUSTED : IP_UNTRUSTED;
418 * Return last address of proxy that is not trusted.
419 * Do not accept "all" keyword here.
421 static const char *last_not_in_array(array *a, plugin_data *p)
423 array *forwarder = p->conf.forwarder;
424 int i;
426 for (i = a->used - 1; i >= 0; i--) {
427 data_string *ds = (data_string *)a->data[i];
428 if (!array_get_element_klen(forwarder, CONST_BUF_LEN(ds->value))) {
429 return ds->value->ptr;
432 return NULL;
435 static void ipstr_to_sockaddr(server *srv, const char *host, sock_addr *sock) {
436 #ifdef HAVE_IPV6
437 struct addrinfo hints, *addrlist = NULL;
438 int result;
440 memset(&hints, 0, sizeof(hints));
441 sock->plain.sa_family = AF_UNSPEC;
443 #ifndef AI_NUMERICSERV
445 * quoting $ man getaddrinfo
447 * NOTES
448 * AI_ADDRCONFIG, AI_ALL, and AI_V4MAPPED are available since glibc 2.3.3.
449 * AI_NUMERICSERV is available since glibc 2.3.4.
451 #define AI_NUMERICSERV 0
452 #endif
453 hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV;
455 errno = 0;
456 result = getaddrinfo(host, NULL, &hints, &addrlist);
458 if (result != 0) {
459 log_error_write(srv, __FILE__, __LINE__, "SSSs(S)",
460 "could not parse ip address ", host, " because ", gai_strerror(result), strerror(errno));
461 } else if (addrlist == NULL) {
462 log_error_write(srv, __FILE__, __LINE__, "SSS",
463 "Problem in parsing ip address ", host, ": succeeded, but no information returned");
464 } else switch (addrlist->ai_family) {
465 case AF_INET:
466 memcpy(&sock->ipv4, addrlist->ai_addr, sizeof(sock->ipv4));
467 force_assert(AF_INET == sock->plain.sa_family);
468 break;
469 case AF_INET6:
470 memcpy(&sock->ipv6, addrlist->ai_addr, sizeof(sock->ipv6));
471 force_assert(AF_INET6 == sock->plain.sa_family);
472 break;
473 default:
474 log_error_write(srv, __FILE__, __LINE__, "SSS",
475 "Problem in parsing ip address ", host, ": succeeded, but unknown family");
478 freeaddrinfo(addrlist);
479 #else
480 UNUSED(srv);
481 sock->ipv4.sin_addr.s_addr = inet_addr(host);
482 sock->plain.sa_family = (sock->ipv4.sin_addr.s_addr == 0xFFFFFFFF) ? AF_UNSPEC : AF_INET;
483 #endif
486 static int mod_extforward_set_addr(server *srv, connection *con, plugin_data *p, const char *addr) {
487 sock_addr sock;
488 handler_ctx *hctx = con->plugin_ctx[p->id];
490 if (con->conf.log_request_handling) {
491 log_error_write(srv, __FILE__, __LINE__, "ss", "using address:", addr);
494 sock.plain.sa_family = AF_UNSPEC;
495 ipstr_to_sockaddr(srv, addr, &sock);
496 if (sock.plain.sa_family == AF_UNSPEC) return 0;
498 /* we found the remote address, modify current connection and save the old address */
499 if (hctx) {
500 if (hctx->saved_remote_addr_buf) {
501 if (con->conf.log_request_handling) {
502 log_error_write(srv, __FILE__, __LINE__, "s",
503 "-- mod_extforward_uri_handler already patched this connection, resetting state");
505 con->dst_addr = hctx->saved_remote_addr;
506 buffer_free(con->dst_addr_buf);
507 con->dst_addr_buf = hctx->saved_remote_addr_buf;
508 hctx->saved_remote_addr_buf = NULL;
510 } else {
511 con->plugin_ctx[p->id] = hctx = handler_ctx_init();
513 /* save old address */
514 if (extforward_check_proxy) {
515 array_set_key_value(con->environment, CONST_STR_LEN("_L_EXTFORWARD_ACTUAL_FOR"), CONST_BUF_LEN(con->dst_addr_buf));
517 hctx->saved_remote_addr = con->dst_addr;
518 hctx->saved_remote_addr_buf = con->dst_addr_buf;
519 /* patch connection address */
520 con->dst_addr = sock;
521 con->dst_addr_buf = buffer_init_string(addr);
523 if (con->conf.log_request_handling) {
524 log_error_write(srv, __FILE__, __LINE__, "ss",
525 "patching con->dst_addr_buf for the accesslog:", addr);
528 /* Now, clean the conf_cond cache, because we may have changed the results of tests */
529 config_cond_cache_reset_item(srv, con, COMP_HTTP_REMOTE_IP);
531 return 1;
534 static void mod_extforward_set_proto(server *srv, connection *con, const char *proto, size_t protolen) {
535 if (0 != protolen && !buffer_is_equal_caseless_string(con->uri.scheme, proto, protolen)) {
536 /* update scheme if X-Forwarded-Proto is set
537 * Limitations:
538 * - Only "http" or "https" are currently accepted since the request to lighttpd currently has to
539 * be HTTP/1.0 or HTTP/1.1 using http or https. If this is changed, then the scheme from this
540 * untrusted header must be checked to contain only alphanumeric characters, and to be a
541 * reasonable length, e.g. < 256 chars.
542 * - con->uri.scheme is not reset in mod_extforward_restore() but is currently not an issues since
543 * con->uri.scheme will be reset by next request. If a new module uses con->uri.scheme in the
544 * handle_request_done hook, then should evaluate if that module should use the forwarded value
545 * (probably) or the original value.
547 if (extforward_check_proxy) {
548 array_set_key_value(con->environment, CONST_STR_LEN("_L_EXTFORWARD_ACTUAL_PROTO"), CONST_BUF_LEN(con->uri.scheme));
550 if (0 == buffer_caseless_compare(proto, protolen, CONST_STR_LEN("https"))) {
551 buffer_copy_string_len(con->uri.scheme, CONST_STR_LEN("https"));
552 config_cond_cache_reset_item(srv, con, COMP_HTTP_SCHEME);
553 } else if (0 == buffer_caseless_compare(proto, protolen, CONST_STR_LEN("http"))) {
554 buffer_copy_string_len(con->uri.scheme, CONST_STR_LEN("http"));
555 config_cond_cache_reset_item(srv, con, COMP_HTTP_SCHEME);
560 static handler_t mod_extforward_X_Forwarded_For(server *srv, connection *con, plugin_data *p, buffer *x_forwarded_for) {
561 /* build forward_array from forwarded data_string */
562 array *forward_array = extract_forward_array(x_forwarded_for);
563 const char *real_remote_addr = last_not_in_array(forward_array, p);
564 if (real_remote_addr != NULL) { /* parsed */
565 /* get scheme if X-Forwarded-Proto is set
566 * Limitations:
567 * - X-Forwarded-Proto may or may not be set by proxies, even if X-Forwarded-For is set
568 * - X-Forwarded-Proto may be a comma-separated list if there are multiple proxies,
569 * but the historical behavior of the code below only honored it if there was exactly one value
570 * (not done: walking backwards in X-Forwarded-Proto the same num of steps
571 * as in X-Forwarded-For to find proto set by last trusted proxy)
573 data_string *x_forwarded_proto = (data_string *)array_get_element(con->request.headers, "X-Forwarded-Proto");
574 if (mod_extforward_set_addr(srv, con, p, real_remote_addr) && NULL != x_forwarded_proto) {
575 mod_extforward_set_proto(srv, con, CONST_BUF_LEN(x_forwarded_proto->value));
578 array_free(forward_array);
579 return HANDLER_GO_ON;
582 static int find_end_quoted_string (const char * const s, int i) {
583 do {
584 ++i;
585 } while (s[i] != '"' && s[i] != '\0' && (s[i] != '\\' || s[++i] != '\0'));
586 return i;
589 static int find_next_semicolon_or_comma_or_eq (const char * const s, int i) {
590 for (; s[i] != '=' && s[i] != ';' && s[i] != ',' && s[i] != '\0'; ++i) {
591 if (s[i] == '"') {
592 i = find_end_quoted_string(s, i);
593 if (s[i] == '\0') return -1;
596 return i;
599 static int find_next_semicolon_or_comma (const char * const s, int i) {
600 for (; s[i] != ';' && s[i] != ',' && s[i] != '\0'; ++i) {
601 if (s[i] == '"') {
602 i = find_end_quoted_string(s, i);
603 if (s[i] == '\0') return -1;
606 return i;
609 static int buffer_backslash_unescape (buffer * const b) {
610 /* (future: might move to buffer.c) */
611 size_t j = 0;
612 size_t len = buffer_string_length(b);
613 char *p = memchr(b->ptr, '\\', len);
615 if (NULL == p) return 1; /*(nothing to do)*/
617 len -= (size_t)(p - b->ptr);
618 for (size_t i = 0; i < len; ++i) {
619 if (p[i] == '\\') {
620 if (++i == len) return 0; /*(invalid trailing backslash)*/
622 p[j++] = p[i];
624 buffer_string_set_length(b, (size_t)(p+j - b->ptr));
625 return 1;
628 static handler_t mod_extforward_Forwarded (server *srv, connection *con, plugin_data *p, buffer *forwarded) {
629 /* HTTP list need not consist of param=value tokens,
630 * but this routine expect such for HTTP Forwarded header
631 * Since info in each set of params is only used if from
632 * admin-specified trusted proxy:
633 * - invalid param=value tokens are ignored and skipped
634 * - not checking "for" exists in each set of params
635 * - not checking for duplicated params in each set of params
636 * - not checking canonical form of addr (also might be obfuscated)
637 * - obfuscated tokens permitted in chain, though end of trust is expected
638 * to be non-obfuscated IP for mod_extforward to masquerade as remote IP
639 * future: since (potentially) trusted proxies begin at end of string,
640 * it might be better to parse from end of string rather than parsing from
641 * beginning. Doing so would also allow reducing arbitrary param limit
642 * to number of params permitted per proxy.
644 char * const s = forwarded->ptr;
645 int i = 0, j = -1, v, vlen, k, klen;
646 int used = (int)buffer_string_length(forwarded);
647 int ofor = -1, oproto, ohost, oby, oremote_user;
648 int offsets[256];/*(~50 params is more than reasonably expected to handle)*/
649 while (i < used) {
650 while (s[i] == ' ' || s[i] == '\t') ++i;
651 if (s[i] == ';') { ++i; continue; }
652 if (s[i] == ',') {
653 if (j >= (int)(sizeof(offsets)/sizeof(int))) break;
654 offsets[++j] = -1; /*("offset" separating params from next proxy)*/
655 ++i;
656 continue;
658 if (s[i] == '\0') break;
660 k = i;
661 i = find_next_semicolon_or_comma_or_eq(s, i);
662 if (i < 0) {
663 /*(reject IP spoofing if attacker sets improper quoted-string)*/
664 log_error_write(srv, __FILE__, __LINE__, "s",
665 "invalid quoted-string in Forwarded header");
666 con->http_status = 400; /* Bad Request */
667 con->mode = DIRECT;
668 return HANDLER_FINISHED;
670 if (s[i] != '=') continue;
671 klen = i - k;
672 v = ++i;
673 i = find_next_semicolon_or_comma(s, i);
674 if (i < 0) {
675 /*(reject IP spoofing if attacker sets improper quoted-string)*/
676 log_error_write(srv, __FILE__, __LINE__, "s",
677 "invalid quoted-string in Forwarded header");
678 con->http_status = 400; /* Bad Request */
679 con->mode = DIRECT;
680 return HANDLER_FINISHED;
682 vlen = i - v; /* might be 0 */
684 /* have k, klen, v, vlen
685 * (might contain quoted string) (contents not validated or decoded)
686 * (might be repeated k)
688 if (0 == klen) continue; /* invalid k */
689 if (j >= (int)(sizeof(offsets)/sizeof(int))-4) break;
690 offsets[j+1] = k;
691 offsets[j+2] = klen;
692 offsets[j+3] = v;
693 offsets[j+4] = vlen;
694 j += 4;
697 if (j >= (int)(sizeof(offsets)/sizeof(int))-4) {
698 /* error processing Forwarded; too many params; fail closed */
699 log_error_write(srv, __FILE__, __LINE__, "s",
700 "Too many params in Forwarded header");
701 con->http_status = 400; /* Bad Request */
702 con->mode = DIRECT;
703 return HANDLER_FINISHED;
706 if (-1 == j) return HANDLER_GO_ON; /* make no changes */
707 used = j+1;
708 offsets[used] = -1; /* mark end of last set of params */
710 while (j > 0) { /*(param=value pairs, so j > 0, not j >= 0)*/
711 if (-1 == offsets[j]) { --j; continue; }
712 do {
713 j -= 3; /*(k, klen, v, vlen come in sets of 4)*/
714 } while ((3 != offsets[j+1] /* 3 == sizeof("for")-1 */
715 || 0 != buffer_caseless_compare(s+offsets[j], 3, "for", 3))
716 && 0 != j-- && -1 != offsets[j]);
717 if (j < 0) break;
718 if (-1 == offsets[j]) { --j; continue; }
720 /* remove trailing spaces/tabs and double-quotes from string
721 * (note: not unescaping backslash escapes in quoted string) */
722 v = offsets[j+2];
723 vlen = v + offsets[j+3];
724 while (vlen > v && (s[vlen-1] == ' ' || s[vlen-1] == '\t')) --vlen;
725 if (vlen > v+1 && s[v] == '"' && s[vlen-1] == '"') {
726 offsets[j+2] = ++v;
727 --vlen;
728 if (s[v] == '[') {
729 /* remove "[]" surrounding IPv6, as well as (optional) port
730 * (assumes properly formatted IPv6 addr from trusted proxy) */
731 ++v;
732 do { --vlen; } while (vlen > v && s[vlen] != ']');
733 if (v == vlen) {
734 log_error_write(srv, __FILE__, __LINE__, "s",
735 "Invalid IPv6 addr in Forwarded header");
736 con->http_status = 400; /* Bad Request */
737 con->mode = DIRECT;
738 return HANDLER_FINISHED;
741 else if (s[v] != '_' && s[v] != '/' && s[v] != 'u') {
742 /* remove (optional) port from non-obfuscated IPv4 */
743 for (klen=vlen, vlen=v; vlen < klen && s[vlen] != ':'; ++vlen) ;
745 offsets[j+2] = v;
747 offsets[j+3] = vlen - v;
749 /* obfuscated ipstr and obfuscated port are also accepted here, as
750 * is path to unix domain socket, but note that backslash escapes
751 * in quoted-string were not unescaped above. Also, if obfuscated
752 * identifiers are rotated by proxies as recommended by RFC, then
753 * maintaining list of trusted identifiers is non-trivial and is not
754 * attempted by this module. */
756 if (v != vlen) {
757 int trusted = (NULL != array_get_element_klen(p->conf.forwarder, s+v, vlen-v));
759 if (s[v] != '_' && s[v] != '/'
760 && (7 != (vlen - v) || 0 != memcmp(s+v, "unknown", 7))) {
761 ofor = j; /* save most recent non-obfuscated ipstr */
764 if (!trusted) break;
767 do { --j; } while (j > 0 && -1 != offsets[j]);
768 if (j <= 0) break;
769 --j;
772 if (-1 != ofor) {
773 /* C funcs getaddrinfo(), inet_addr() require '\0'-terminated IP str */
774 char *ipend = s+offsets[ofor+2]+offsets[ofor+3];
775 char c = *ipend;
776 int rc;
777 *ipend = '\0';
778 rc = mod_extforward_set_addr(srv, con, p, s+offsets[ofor+2]);
779 *ipend = c;
780 if (!rc) return HANDLER_GO_ON; /* invalid addr; make no changes */
782 else {
783 return HANDLER_GO_ON; /* make no changes */
786 /* parse out params associated with for=<ip> addr set above */
787 oproto = ohost = oby = oremote_user = -1;
788 j = ofor;
789 if (j > 0) { do { --j; } while (j > 0 && -1 != offsets[j]); }
790 if (-1 == offsets[j]) ++j;
791 if (j == ofor) j += 4;
792 for (; -1 != offsets[j]; j+=4) { /*(k, klen, v, vlen come in sets of 4)*/
793 switch (offsets[j+1]) {
794 #if 0
795 case 2:
796 if (0 == buffer_caseless_compare(s+offsets[j],2,"by",2))
797 oby = j;
798 break;
799 #endif
800 #if 0
801 /*(already handled above to find IP prior to earliest trusted proxy)*/
802 case 3:
803 if (0 == buffer_caseless_compare(s+offsets[j],3,"for",3))
804 ofor = j;
805 break;
806 #endif
807 case 4:
808 if (0 == buffer_caseless_compare(s+offsets[j],4,"host",4))
809 ohost = j;
810 break;
811 case 5:
812 if (0 == buffer_caseless_compare(s+offsets[j],5,"proto",5))
813 oproto = j;
814 break;
815 case 11:
816 if (0 == buffer_caseless_compare(s+offsets[j],11,"remote_user",11))
817 oremote_user = j;
818 break;
819 default:
820 break;
823 i = ++j;
825 if (-1 != oproto) {
826 /* remove trailing spaces/tabs, and double-quotes from proto
827 * (note: not unescaping backslash escapes in quoted string) */
828 v = offsets[oproto+2];
829 vlen = v + offsets[oproto+3];
830 while (vlen > v && (s[vlen-1] == ' ' || s[vlen-1] == '\t')) --vlen;
831 if (vlen > v+1 && s[v] == '"' && s[vlen-1] == '"') { ++v; --vlen; }
832 mod_extforward_set_proto(srv, con, s+v, vlen-v);
835 if (p->conf.opts & PROXY_FORWARDED_HOST) {
836 /* Limitations:
837 * - con->request.http_host is not reset in mod_extforward_restore()
838 * but is currently not an issues since con->request.http_host will be
839 * reset by next request. If a new module uses con->request.http_host
840 * in the handle_request_done hook, then should evaluate if that
841 * module should use the forwarded value (probably) or original value.
842 * - due to need to decode and unescape host=..., some extra work is
843 * done in the case where host matches current Host header.
844 * future: might add code to check if Host has actually changed or not
846 * note: change host after mod_extforward_set_proto() since that may
847 * affect scheme port used in http_request_host_policy() host
848 * normalization
851 /* find host param set by earliest trusted proxy in proxy chain
852 * (host might be changed anywhere along the chain) */
853 for (j = i; j < used && -1 == ohost; ) {
854 if (-1 == offsets[j]) { ++j; continue; }
855 if (4 == offsets[j+1]
856 && 0 == buffer_caseless_compare(s+offsets[j], 4, "host", 4))
857 ohost = j;
858 j += 4; /*(k, klen, v, vlen come in sets of 4)*/
860 if (-1 != ohost) {
861 if (extforward_check_proxy
862 && !buffer_string_is_empty(con->request.http_host)) {
863 array_set_key_value(con->environment,
864 CONST_STR_LEN("_L_EXTFORWARD_ACTUAL_HOST"),
865 CONST_BUF_LEN(con->request.http_host));
867 /* remove trailing spaces/tabs, and double-quotes from host */
868 v = offsets[ohost+2];
869 vlen = v + offsets[ohost+3];
870 while (vlen > v && (s[vlen-1] == ' ' || s[vlen-1] == '\t')) --vlen;
871 if (vlen > v+1 && s[v] == '"' && s[vlen-1] == '"') {
872 ++v; --vlen;
873 buffer_copy_string_len(con->request.http_host, s+v, vlen-v);
874 if (!buffer_backslash_unescape(con->request.http_host)) {
875 log_error_write(srv, __FILE__, __LINE__, "s",
876 "invalid host= value in Forwarded header");
877 con->http_status = 400; /* Bad Request */
878 con->mode = DIRECT;
879 return HANDLER_FINISHED;
882 else {
883 buffer_copy_string_len(con->request.http_host, s+v, vlen-v);
886 if (0 != http_request_host_policy(con, con->request.http_host,
887 con->uri.scheme)) {
888 /*(reject invalid chars in Host)*/
889 log_error_write(srv, __FILE__, __LINE__, "s",
890 "invalid host= value in Forwarded header");
891 con->http_status = 400; /* Bad Request */
892 con->mode = DIRECT;
893 return HANDLER_FINISHED;
896 config_cond_cache_reset_item(srv, con, COMP_HTTP_HOST);
900 if (p->conf.opts & PROXY_FORWARDED_REMOTE_USER) {
901 /* find remote_user param set by closest proxy
902 * (auth may have been handled by any trusted proxy in proxy chain) */
903 for (j = i; j < used; ) {
904 if (-1 == offsets[j]) { ++j; continue; }
905 if (11 == offsets[j+1]
906 && 0==buffer_caseless_compare(s+offsets[j],11,"remote_user",11))
907 oremote_user = j;
908 j += 4; /*(k, klen, v, vlen come in sets of 4)*/
910 if (-1 != oremote_user) {
911 /* ???: should we also support param for auth_type ??? */
912 /* remove trailing spaces/tabs, and double-quotes from remote_user*/
913 v = offsets[oremote_user+2];
914 vlen = v + offsets[oremote_user+3];
915 while (vlen > v && (s[vlen-1] == ' ' || s[vlen-1] == '\t')) --vlen;
916 if (vlen > v+1 && s[v] == '"' && s[vlen-1] == '"') {
917 data_string *dsuser;
918 ++v; --vlen;
919 array_set_key_value(con->environment,
920 CONST_STR_LEN("REMOTE_USER"), s+v, vlen-v);
921 dsuser = (data_string *)
922 array_get_element(con->environment, "REMOTE_USER");
923 force_assert(NULL != dsuser);
924 if (!buffer_backslash_unescape(dsuser->value)) {
925 log_error_write(srv, __FILE__, __LINE__, "s",
926 "invalid remote_user= value in Forwarded header");
927 con->http_status = 400; /* Bad Request */
928 con->mode = DIRECT;
929 return HANDLER_FINISHED;
932 else {
933 array_set_key_value(con->environment,
934 CONST_STR_LEN("REMOTE_USER"), s+v, vlen-v);
939 #if 0
940 if ((p->conf.opts & PROXY_FORWARDED_CREATE_XFF)
941 && NULL == array_get_element(con->request.headers, "X-Forwarded-For")) {
942 /* create X-Forwarded-For if not present
943 * (and at least original connecting IP is a trusted proxy) */
944 buffer *xff;
945 data_string *dsxff = (data_string *)
946 array_get_unused_element(con->request.headers, TYPE_STRING);
947 if (NULL == dsxff) dsxff = data_string_init();
948 buffer_copy_string_len(dsxff->key, CONST_STR_LEN("X-Forwarded-For"));
949 array_insert_unique(con->request.headers, (data_unset *)dsxff);
950 xff = dsxff->value;
951 for (j = 0; j < used; ) {
952 if (-1 == offsets[j]) { ++j; continue; }
953 if (3 == offsets[j+1]
954 && 0 == buffer_caseless_compare(s+offsets[j], 3, "for", 3)) {
955 if (!buffer_string_is_empty(xff))
956 buffer_append_string_len(xff, CONST_STR_LEN(", "));
957 /* quoted-string, IPv6 brackets, and :port already removed */
958 v = offsets[j+2];
959 vlen = offsets[j+3];
960 buffer_append_string_len(xff, s+v, vlen);
961 if (s[v-1] != '=') { /*(must have been quoted-string)*/
962 char *x =
963 memchr(xff->ptr+buffer_string_length(xff)-vlen,'\\',vlen);
964 if (NULL != x) { /* backslash unescape in-place */
965 for (v = 0; x[v]; ++x) {
966 if (x[v] == '\\' && x[++v] == '\0')
967 break; /*(invalid trailing backslash)*/
968 *x = x[v];
970 buffer_string_set_length(xff, x - xff->ptr);
973 /* skip to next group; take first "for=..." in group
974 * (should be 0 or 1 "for=..." per group, but not trusted) */
975 do { j += 4; } while (-1 != offsets[j]);
976 ++j;
977 continue;
979 j += 4; /*(k, klen, v, vlen come in sets of 4)*/
982 #endif
984 return HANDLER_GO_ON;
987 URIHANDLER_FUNC(mod_extforward_uri_handler) {
988 plugin_data *p = p_d;
989 data_string *forwarded = NULL;
990 handler_ctx *hctx = con->plugin_ctx[p->id];
992 mod_extforward_patch_connection(srv, con, p);
994 if (con->conf.log_request_handling) {
995 log_error_write(srv, __FILE__, __LINE__, "s",
996 "-- mod_extforward_uri_handler called");
999 if (NULL != hctx) {
1000 /* XXX: future: add config option to enable
1001 * and replace above with: if (p->conf.???)
1002 * similar to ssl.verifyclient.username */
1003 #if 0
1004 data_string *ds;
1005 if (NULL != hctx && hctx->ssl_client_verify && NULL != hctx->env
1006 && NULL != (ds = (data_string *)array_get_element(hctx->env, "SSL_CLIENT_S_DN_CN"))) {
1007 array_set_key_value(con->environment,
1008 CONST_STR_LEN("SSL_CLIENT_VERIFY"),
1009 CONST_STR_LEN("SUCCESS"));
1010 array_set_key_value(con->environment,
1011 CONST_STR_LEN("REMOTE_USER"),
1012 CONST_BUF_LEN(ds->value));
1013 array_set_key_value(con->environment,
1014 CONST_STR_LEN("AUTH_TYPE"),
1015 CONST_STR_LEN("SSL_CLIENT_VERIFY"));
1016 } else {
1017 array_set_key_value(con->environment,
1018 CONST_STR_LEN("SSL_CLIENT_VERIFY"),
1019 CONST_STR_LEN("NONE"));
1021 #endif
1024 for (size_t k = 0; k < p->conf.headers->used && NULL == forwarded; ++k) {
1025 forwarded = (data_string *) array_get_element_klen(con->request.headers, CONST_BUF_LEN(((data_string *)p->conf.headers->data[k])->value));
1027 if (NULL == forwarded) {
1028 if (con->conf.log_request_handling) {
1029 log_error_write(srv, __FILE__, __LINE__, "s", "no forward header found, skipping");
1032 return HANDLER_GO_ON;
1035 /* if the remote ip itself is not trusted, then do nothing */
1036 if (IP_UNTRUSTED == is_proxy_trusted(con->dst_addr_buf, p)) {
1037 if (con->conf.log_request_handling) {
1038 log_error_write(srv, __FILE__, __LINE__, "sbs",
1039 "remote address", con->dst_addr_buf, "is NOT a trusted proxy, skipping");
1042 return HANDLER_GO_ON;
1045 if (buffer_is_equal_caseless_string(forwarded->key, CONST_STR_LEN("Forwarded"))) {
1046 return mod_extforward_Forwarded(srv, con, p, forwarded->value);
1049 return mod_extforward_X_Forwarded_For(srv, con, p, forwarded->value);
1053 CONNECTION_FUNC(mod_extforward_handle_request_env) {
1054 plugin_data *p = p_d;
1055 handler_ctx *hctx = con->plugin_ctx[p->id];
1056 UNUSED(srv);
1057 if (NULL == hctx || NULL == hctx->env) return HANDLER_GO_ON;
1058 for (size_t i=0; i < hctx->env->used; ++i) {
1059 /* note: replaces values which may have been set by mod_openssl
1060 * (when mod_extforward is listed after mod_openssl in server.modules)*/
1061 data_string *ds = (data_string *)hctx->env->data[i];
1062 array_set_key_value(con->environment,
1063 CONST_BUF_LEN(ds->key), CONST_BUF_LEN(ds->value));
1065 return HANDLER_GO_ON;
1069 CONNECTION_FUNC(mod_extforward_restore) {
1070 plugin_data *p = p_d;
1071 handler_ctx *hctx = con->plugin_ctx[p->id];
1073 if (!hctx) return HANDLER_GO_ON;
1075 if (NULL != hctx->saved_network_read) {
1076 con->network_read = hctx->saved_network_read;
1077 hctx->saved_network_read = NULL;
1080 if (NULL != hctx->saved_remote_addr_buf) {
1081 con->dst_addr = hctx->saved_remote_addr;
1082 buffer_free(con->dst_addr_buf);
1083 con->dst_addr_buf = hctx->saved_remote_addr_buf;
1084 hctx->saved_remote_addr_buf = NULL;
1085 /* Now, clean the conf_cond cache, because we may have changed the results of tests */
1086 config_cond_cache_reset_item(srv, con, COMP_HTTP_REMOTE_IP);
1089 if (NULL == hctx->env) {
1090 handler_ctx_free(hctx);
1091 con->plugin_ctx[p->id] = NULL;
1094 return HANDLER_GO_ON;
1098 CONNECTION_FUNC(mod_extforward_handle_con_close)
1100 plugin_data *p = p_d;
1101 handler_ctx *hctx = con->plugin_ctx[p->id];
1102 UNUSED(srv);
1103 if (NULL != hctx) {
1104 if (NULL != hctx->saved_network_read) {
1105 con->network_read = hctx->saved_network_read;
1107 if (NULL != hctx->saved_remote_addr_buf) {
1108 con->dst_addr = hctx->saved_remote_addr;
1109 buffer_free(con->dst_addr_buf);
1110 con->dst_addr_buf = hctx->saved_remote_addr_buf;
1112 if (NULL != hctx->env) {
1113 array_free(hctx->env);
1115 handler_ctx_free(hctx);
1116 con->plugin_ctx[p->id] = NULL;
1119 return HANDLER_GO_ON;
1123 static int mod_extforward_network_read (server *srv, connection *con, chunkqueue *cq, off_t max_bytes);
1125 CONNECTION_FUNC(mod_extforward_handle_con_accept)
1127 plugin_data *p = p_d;
1128 mod_extforward_patch_connection(srv, con, p);
1129 if (!p->conf.hap_PROXY) return HANDLER_GO_ON;
1130 if (IP_TRUSTED == is_proxy_trusted(con->dst_addr_buf, p)) {
1131 handler_ctx *hctx = handler_ctx_init();
1132 con->plugin_ctx[p->id] = hctx;
1133 hctx->saved_network_read = con->network_read;
1134 con->network_read = mod_extforward_network_read;
1136 else {
1137 if (con->conf.log_request_handling) {
1138 log_error_write(srv, __FILE__, __LINE__, "sbs",
1139 "remote address", con->dst_addr_buf,
1140 "is NOT a trusted proxy, skipping");
1143 return HANDLER_GO_ON;
1147 /* this function is called at dlopen() time and inits the callbacks */
1149 int mod_extforward_plugin_init(plugin *p);
1150 int mod_extforward_plugin_init(plugin *p) {
1151 p->version = LIGHTTPD_VERSION_ID;
1152 p->name = buffer_init_string("extforward");
1154 p->init = mod_extforward_init;
1155 p->handle_connection_accept = mod_extforward_handle_con_accept;
1156 p->handle_uri_raw = mod_extforward_uri_handler;
1157 p->handle_request_env = mod_extforward_handle_request_env;
1158 p->handle_request_done = mod_extforward_restore;
1159 p->connection_reset = mod_extforward_restore;
1160 p->handle_connection_close = mod_extforward_handle_con_close;
1161 p->set_defaults = mod_extforward_set_defaults;
1162 p->cleanup = mod_extforward_free;
1164 p->data = NULL;
1166 return 0;
1172 /* Modified from:
1173 * http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
1175 9. Sample code
1177 The code below is an example of how a receiver may deal with both versions of
1178 the protocol header for TCP over IPv4 or IPv6. The function is supposed to be
1179 called upon a read event. Addresses may be directly copied into their final
1180 memory location since they're transported in network byte order. The sending
1181 side is even simpler and can easily be deduced from this sample code.
1185 union hap_PROXY_hdr {
1186 struct {
1187 char line[108];
1188 } v1;
1189 struct {
1190 uint8_t sig[12];
1191 uint8_t ver_cmd;
1192 uint8_t fam;
1193 uint16_t len;
1194 union {
1195 struct { /* for TCP/UDP over IPv4, len = 12 */
1196 uint32_t src_addr;
1197 uint32_t dst_addr;
1198 uint16_t src_port;
1199 uint16_t dst_port;
1200 } ip4;
1201 struct { /* for TCP/UDP over IPv6, len = 36 */
1202 uint8_t src_addr[16];
1203 uint8_t dst_addr[16];
1204 uint16_t src_port;
1205 uint16_t dst_port;
1206 } ip6;
1207 struct { /* for AF_UNIX sockets, len = 216 */
1208 uint8_t src_addr[108];
1209 uint8_t dst_addr[108];
1210 } unx;
1211 } addr;
1212 } v2;
1216 If the length specified in the PROXY protocol header indicates that additional
1217 bytes are part of the header beyond the address information, a receiver may
1218 choose to skip over and ignore those bytes, or attempt to interpret those
1219 bytes.
1221 The information in those bytes will be arranged in Type-Length-Value (TLV
1222 vectors) in the following format. The first byte is the Type of the vector.
1223 The second two bytes represent the length in bytes of the value (not included
1224 the Type and Length bytes), and following the length field is the number of
1225 bytes specified by the length.
1227 struct pp2_tlv {
1228 uint8_t type;
1229 uint8_t length_hi;
1230 uint8_t length_lo;
1231 /*uint8_t value[0];*//* C99 zero-length array */
1235 The following types have already been registered for the <type> field :
1238 #define PP2_TYPE_ALPN 0x01
1239 #define PP2_TYPE_AUTHORITY 0x02
1240 #define PP2_TYPE_CRC32C 0x03
1241 #define PP2_TYPE_NOOP 0x04
1242 #define PP2_TYPE_SSL 0x20
1243 #define PP2_SUBTYPE_SSL_VERSION 0x21
1244 #define PP2_SUBTYPE_SSL_CN 0x22
1245 #define PP2_SUBTYPE_SSL_CIPHER 0x23
1246 #define PP2_SUBTYPE_SSL_SIG_ALG 0x24
1247 #define PP2_SUBTYPE_SSL_KEY_ALG 0x25
1248 #define PP2_TYPE_NETNS 0x30
1251 For the type PP2_TYPE_SSL, the value is itselv a defined like this :
1254 struct pp2_tlv_ssl {
1255 uint8_t client;
1256 uint32_t verify;
1257 /*struct pp2_tlv sub_tlv[0];*//* C99 zero-length array */
1261 And the <client> field is made of a bit field from the following values,
1262 indicating which element is present :
1265 #define PP2_CLIENT_SSL 0x01
1266 #define PP2_CLIENT_CERT_CONN 0x02
1267 #define PP2_CLIENT_CERT_SESS 0x04
1272 #ifndef MSG_DONTWAIT
1273 #define MSG_DONTWAIT 0
1274 #endif
1275 #ifndef MSG_NOSIGNAL
1276 #define MSG_NOSIGNAL 0
1277 #endif
1279 /* returns 0 if needs to poll, <0 upon error or >0 is protocol vers (success) */
1280 static int hap_PROXY_recv (const int fd, union hap_PROXY_hdr * const hdr)
1282 static const char v2sig[12] =
1283 "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A";
1285 ssize_t ret;
1286 size_t sz;
1287 int ver;
1289 do {
1290 ret = recv(fd, hdr, sizeof(*hdr), MSG_PEEK|MSG_DONTWAIT|MSG_NOSIGNAL);
1291 } while (-1 == ret && errno == EINTR);
1293 if (-1 == ret)
1294 return (errno == EAGAIN
1295 #ifdef EWOULDBLOCK
1296 #if EAGAIN != EWOULDBLOCK
1297 || errno == EWOULDBLOCK
1298 #endif
1299 #endif
1300 ) ? 0 : -1;
1302 if (ret >= 16 && 0 == memcmp(&hdr->v2, v2sig, 12)
1303 && (hdr->v2.ver_cmd & 0xF0) == 0x20) {
1304 ver = 2;
1305 sz = 16 + (size_t)ntohs(hdr->v2.len);
1306 if ((size_t)ret < sz)
1307 return -2; /* truncated or too large header */
1309 switch (hdr->v2.ver_cmd & 0xF) {
1310 case 0x01: break; /* PROXY command */
1311 case 0x00: break; /* LOCAL command */
1312 default: return -2; /* not a supported command */
1315 else if (ret >= 8 && 0 == memcmp(hdr->v1.line, "PROXY", 5)) {
1316 const char *end = memchr(hdr->v1.line, '\r', ret - 1);
1317 if (!end || end[1] != '\n')
1318 return -2; /* partial or invalid header */
1319 ver = 1;
1320 sz = (size_t)(end + 2 - hdr->v1.line); /* skip header + CRLF */
1322 else {
1323 /* Wrong protocol */
1324 return -2;
1327 /* we need to consume the appropriate amount of data from the socket
1328 * (overwrites existing contents of hdr with same data) */
1329 do {
1330 ret = recv(fd, hdr, sz, MSG_DONTWAIT|MSG_NOSIGNAL);
1331 } while (-1 == ret && errno == EINTR);
1332 if (ret < 0) return -1;
1333 if (1 == ver) hdr->v1.line[sz-2] = '\0'; /*terminate str to ease parsing*/
1334 return ver;
1338 static int mod_extforward_hap_PROXY_v1 (connection * const con,
1339 union hap_PROXY_hdr * const hdr)
1341 /* samples
1342 * "PROXY TCP4 255.255.255.255 255.255.255.255 65535 65535\r\n"
1343 * "PROXY TCP6 ffff:f...f:ffff ffff:f...f:ffff 65535 65535\r\n"
1344 * "PROXY UNKNOWN\r\n"
1345 * "PROXY UNKNOWN ffff:f...f:ffff ffff:f...f:ffff 65535 65535\r\n"
1347 char *s = hdr->v1.line + sizeof("PROXY")-1; /*checked in hap_PROXY_recv()*/
1348 char *src_addr, *dst_addr, *src_port, *dst_port, *e;
1349 int family;
1350 long src_lport, dst_lport;
1351 if (*s != ' ') return -1;
1352 ++s;
1353 if (s[0] == 'T' && s[1] == 'C' && s[2] == 'P' && s[4] == ' ') {
1354 if (s[3] == '4') {
1355 family = AF_INET;
1356 } else if (s[3] == '6') {
1357 family = AF_INET6;
1359 else {
1360 return -1;
1362 s += 5;
1364 else if (0 == memcmp(s, "UNKNOWN", sizeof("UNKNOWN")-1)
1365 && (s[7] == '\0' || s[7] == ' ')) {
1366 return 0; /* keep local connection address */
1368 else {
1369 return -1;
1372 /*(strsep() should be fairly portable, but is not standard)*/
1373 src_addr = s;
1374 dst_addr = strchr(src_addr, ' ');
1375 if (NULL == dst_addr) return -1;
1376 *dst_addr++ = '\0';
1377 src_port = strchr(dst_addr, ' ');
1378 if (NULL == src_port) return -1;
1379 *src_port++ = '\0';
1380 dst_port = strchr(src_port, ' ');
1381 if (NULL == dst_port) return -1;
1382 *dst_port++ = '\0';
1384 src_lport = strtol(src_port, &e, 10);
1385 if (src_lport <= 0 || src_lport > USHRT_MAX || *e != '\0') return -1;
1386 dst_lport = strtol(dst_port, &e, 10);
1387 if (dst_lport <= 0 || dst_lport > USHRT_MAX || *e != '\0') return -1;
1389 if (1 != sock_addr_inet_pton(&con->dst_addr,
1390 src_addr, family, (unsigned short)src_lport))
1391 return -1;
1392 /* Forwarded by=... could be saved here.
1393 * (see additional comments in mod_extforward_hap_PROXY_v2()) */
1395 /* re-parse addr to string to normalize
1396 * (instead of trusting PROXY to provide canonicalized src_addr string)
1397 * (should prefer PROXY v2 protocol if concerned about performance) */
1398 sock_addr_inet_ntop_copy_buffer(con->dst_addr_buf, &con->dst_addr);
1400 return 0;
1404 static int mod_extforward_hap_PROXY_v2 (connection * const con,
1405 union hap_PROXY_hdr * const hdr)
1407 /* If HAProxy-PROXY protocol used, then lighttpd acts as transparent proxy,
1408 * masquerading as servicing the client IP provided in by HAProxy-PROXY hdr.
1409 * The connecting con->dst_addr and con->dst_addr_buf are not saved here,
1410 * so that info is lost unless getsockname() and getpeername() are used.
1411 * One result is that mod_proxy will use the masqueraded IP instead of the
1412 * actual IP when updated Forwarded and X-Forwarded-For (but if actual
1413 * connection IPs needed, better to save the info here rather than use
1414 * syscalls to retrieve the info later).
1415 * (Exception: con->dst_addr can be further changed if mod_extforward parses
1416 * Forwaded or X-Forwarded-For request headers later, after request headers
1417 * have been received.)
1420 /* Forwarded by=... could be saved here. The by param is for backends to be
1421 * able to construct URIs for that interface (interface on server which
1422 * received request and made PROXY connection here), though that server
1423 * should provide that information in updated Forwarded or X-Forwarded-For
1424 * HTTP headers */
1425 /*struct sockaddr_storage by;*/
1427 /* Addresses provided by HAProxy-PROXY protocol are in network byte order.
1428 * Note: addr info is not validated, so do not accept HAProxy-PROXY
1429 * protocol from untrusted servers. For example, untrusted servers from
1430 * which HAProxy-PROXY protocol is accepted (don't do that) could pretend
1431 * to be from the internal network and might thereby bypass security policy.
1434 /* (Clear con->dst_addr with memset() in case actual and proxies IPs
1435 * are different domains, e.g. one is IPv4 and the other is IPv6) */
1437 struct pp2_tlv *tlv;
1438 uint32_t sz = ntohs(hdr->v2.len);
1439 uint32_t len = 0;
1441 switch (hdr->v2.ver_cmd & 0xF) {
1442 case 0x01: break; /* PROXY command */
1443 case 0x00: return 0;/* LOCAL command; keep local connection address */
1444 default: return -1;/* should not happen; validated in hap_PROXY_recv()*/
1447 /* PROXY command */
1449 switch (hdr->v2.fam) {
1450 case 0x11: /* TCPv4 */
1451 memset(&con->dst_addr.ipv4, 0, sizeof(struct sockaddr_in));
1452 con->dst_addr.ipv4.sin_family = AF_INET;
1453 con->dst_addr.ipv4.sin_port = hdr->v2.addr.ip4.src_port;
1454 con->dst_addr.ipv4.sin_addr.s_addr = hdr->v2.addr.ip4.src_addr;
1455 sock_addr_inet_ntop_copy_buffer(con->dst_addr_buf, &con->dst_addr);
1456 #if 0
1457 ((struct sockaddr_in *)&by)->sin_family = AF_INET;
1458 ((struct sockaddr_in *)&by)->sin_addr.s_addr =
1459 hdr->v2.addr.ip4.dst_addr;
1460 ((struct sockaddr_in *)&by)->sin_port =
1461 hdr->v2.addr.ip4.dst_port;
1462 #endif
1463 len = (uint32_t)sizeof(hdr->v2.addr.ip4);
1464 break;
1465 #ifdef HAVE_IPV6
1466 case 0x21: /* TCPv6 */
1467 memset(&con->dst_addr.ipv6, 0, sizeof(struct sockaddr_in6));
1468 con->dst_addr.ipv6.sin6_family = AF_INET6;
1469 con->dst_addr.ipv6.sin6_port = hdr->v2.addr.ip6.src_port;
1470 memcpy(&con->dst_addr.ipv6.sin6_addr, hdr->v2.addr.ip6.src_addr, 16);
1471 sock_addr_inet_ntop_copy_buffer(con->dst_addr_buf, &con->dst_addr);
1472 #if 0
1473 ((struct sockaddr_in6 *)&by)->sin6_family = AF_INET6;
1474 memcpy(&((struct sockaddr_in6 *)&by)->sin6_addr,
1475 hdr->v2.addr.ip6.dst_addr, 16);
1476 ((struct sockaddr_in6 *)&by)->sin6_port =
1477 hdr->v2.addr.ip6.dst_port;
1478 #endif
1479 len = (uint32_t)sizeof(hdr->v2.addr.ip6);
1480 break;
1481 #endif
1482 #ifdef HAVE_SYS_UN_H
1483 case 0x31: /* UNIX domain socket */
1485 char *src_addr = (char *)hdr->v2.addr.unx.src_addr;
1486 char *z = memchr(src_addr, '\0', UNIX_PATH_MAX);
1487 if (NULL == z) return -1; /* invalid addr; too long */
1488 len = (uint32_t)(z - src_addr + 1); /*(+1 for '\0')*/
1489 memset(&con->dst_addr.un, 0, sizeof(struct sockaddr_un));
1490 con->dst_addr.un.sun_family = AF_UNIX;
1491 memcpy(&con->dst_addr.un.sun_path, src_addr, len);
1492 buffer_copy_string_len(con->dst_addr_buf, src_addr, len);
1494 #if 0 /*(dst_addr should be identical to src_addr for AF_UNIX)*/
1495 ((struct sockaddr_un *)&by)->sun_family = AF_UNIX;
1496 memcpy(&((struct sockaddr_un *)&by)->sun_path,
1497 hdr->v2.addr.unx.dst_addr, 108);
1498 #endif
1499 len = (uint32_t)sizeof(hdr->v2.addr.unx);
1500 break;
1501 #endif
1502 default: /* keep local connection address; unsupported protocol */
1503 return 0;
1506 /* (optional) Type-Length-Value (TLV vectors) follow addresses */
1508 tlv = (struct pp2_tlv *)((char *)hdr + 16);
1509 for (sz -= len, len -= 3; sz >= 3; sz -= 3 + len) {
1510 tlv = (struct pp2_tlv *)((char *)tlv + 3 + len);
1511 len = ((uint32_t)tlv->length_hi << 8) | tlv->length_lo;
1512 if (3 + len > sz) break; /*(invalid TLV)*/
1513 switch (tlv->type) {
1514 #if 0 /*(not implemented here)*/
1515 case PP2_TYPE_ALPN:
1516 case PP2_TYPE_AUTHORITY:
1517 case PP2_TYPE_CRC32C:
1518 #endif
1519 case PP2_TYPE_SSL: {
1520 static const uint32_t zero = 0;
1521 handler_ctx *hctx =
1522 con->plugin_ctx[mod_extforward_plugin_data_singleton->id];
1523 struct pp2_tlv_ssl *tlv_ssl =
1524 (struct pp2_tlv_ssl *)(void *)((char *)tlv+3);
1525 struct pp2_tlv *subtlv = tlv;
1526 if (tlv_ssl->client & PP2_CLIENT_SSL) {
1527 buffer_copy_string_len(con->proto, CONST_STR_LEN("https"));
1529 if ((tlv_ssl->client & (PP2_CLIENT_CERT_CONN|PP2_CLIENT_CERT_SESS))
1530 && 0 == memcmp(&tlv_ssl->verify, &zero, 4)) { /* misaligned */
1531 hctx->ssl_client_verify = 1;
1533 for (uint32_t subsz = len-5, n = 5; subsz >= 3; subsz -= 3 + n) {
1534 subtlv = (struct pp2_tlv *)((char *)subtlv + 3 + n);
1535 n = ((uint32_t)subtlv->length_hi << 8) | subtlv->length_lo;
1536 if (3 + n > subsz) break; /*(invalid TLV)*/
1537 if (NULL == hctx->env) hctx->env = array_init();
1538 switch (subtlv->type) {
1539 case PP2_SUBTYPE_SSL_VERSION:
1540 array_set_key_value(hctx->env,
1541 CONST_STR_LEN("SSL_PROTOCOL"),
1542 (char *)subtlv+3, n);
1543 break;
1544 case PP2_SUBTYPE_SSL_CN:
1545 /* (tlv_ssl->client & PP2_CLIENT_CERT_CONN)
1546 * or
1547 * (tlv_ssl->client & PP2_CLIENT_CERT_SESS) */
1548 array_set_key_value(hctx->env,
1549 CONST_STR_LEN("SSL_CLIENT_S_DN_CN"),
1550 (char *)subtlv+3, n);
1551 break;
1552 case PP2_SUBTYPE_SSL_CIPHER:
1553 array_set_key_value(hctx->env,
1554 CONST_STR_LEN("SSL_CIPHER"),
1555 (char *)subtlv+3, n);
1556 break;
1557 case PP2_SUBTYPE_SSL_SIG_ALG:
1558 array_set_key_value(hctx->env,
1559 CONST_STR_LEN("SSL_SERVER_A_SIG"),
1560 (char *)subtlv+3, n);
1561 break;
1562 case PP2_SUBTYPE_SSL_KEY_ALG:
1563 array_set_key_value(hctx->env,
1564 CONST_STR_LEN("SSL_SERVER_A_KEY"),
1565 (char *)subtlv+3, n);
1566 break;
1567 default:
1568 break;
1571 break;
1573 #if 0 /*(not implemented here)*/
1574 case PP2_TYPE_NETNS:
1575 #endif
1576 /*case PP2_TYPE_NOOP:*//* no-op */
1577 default:
1578 break;
1582 return 0;
1586 static int mod_extforward_network_read (server *srv, connection *con,
1587 chunkqueue *cq, off_t max_bytes)
1589 /* XXX: when using hap-PROXY protocol, currently avoid overhead of setting
1590 * _L_ environment variables for mod_proxy to accurately set Forwarded hdr
1591 * In the future, might add config switch to enable doing this extra work */
1593 union hap_PROXY_hdr hdr;
1594 int rc;
1595 switch (hap_PROXY_recv(con->fd, &hdr)) {
1596 case 2: rc = mod_extforward_hap_PROXY_v2(con, &hdr); break;
1597 case 1: rc = mod_extforward_hap_PROXY_v1(con, &hdr); break;
1598 case 0: return 0; /*(errno == EAGAIN || errno == EWOULDBLOCK)*/
1599 case -1: log_error_write(srv, __FILE__, __LINE__, "ss",
1600 "hap-PROXY recv()", strerror(errno));
1601 rc = -1; break;
1602 case -2: log_error_write(srv, __FILE__, __LINE__, "s",
1603 "hap-PROXY proto received "
1604 "invalid/unsupported request");
1605 /* fall through */
1606 default: rc = -1; break;
1609 mod_extforward_restore(srv, con, mod_extforward_plugin_data_singleton);
1610 return (0 == rc) ? con->network_read(srv, con, cq, max_bytes) : rc;