- next is 1.4.56
[lighttpd.git] / src / mod_extforward.c
blobb53e0d52300b52da9b1ff2a1ac6d4d5d3fdcf39f
1 #include "first.h"
3 #include "base.h"
4 #include "log.h"
5 #include "buffer.h"
6 #include "http_header.h"
7 #include "request.h"
8 #include "sock_addr.h"
10 #include "plugin.h"
12 #include "configfile.h"
14 #include <limits.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <errno.h>
19 #include "sys-socket.h"
21 /**
22 * mod_extforward.c for lighttpd, by comman.kang <at> gmail <dot> com
23 * extended, modified by Lionel Elie Mamane (LEM), lionel <at> mamane <dot> lu
24 * support chained proxies by glen@delfi.ee, #1528
26 * Config example:
28 * Trust proxy 10.0.0.232 and 10.0.0.232
29 * extforward.forwarder = ( "10.0.0.232" => "trust",
30 * "10.0.0.233" => "trust" )
32 * Trust all proxies (NOT RECOMMENDED!)
33 * extforward.forwarder = ( "all" => "trust")
35 * Note that "all" has precedence over specific entries,
36 * so "all except" setups will not work.
38 * In case you have chained proxies, you can add all their IP's to the
39 * config. However "all" has effect only on connecting IP, as the
40 * X-Forwarded-For header can not be trusted.
42 * Note: The effect of this module is variable on $HTTP["remotip"] directives and
43 * other module's remote ip dependent actions.
44 * Things done by modules before we change the remoteip or after we reset it will match on the proxy's IP.
45 * Things done in between these two moments will match on the real client's IP.
46 * The moment things are done by a module depends on in which hook it does things and within the same hook
47 * on whether they are before/after us in the module loading order
48 * (order in the server.modules directive in the config file).
50 * Tested behaviours:
52 * mod_access: Will match on the real client.
54 * mod_accesslog:
55 * In order to see the "real" ip address in access log ,
56 * you'll have to load mod_extforward after mod_accesslog.
57 * like this:
59 * server.modules = (
60 * .....
61 * mod_accesslog,
62 * mod_extforward
63 * )
67 /* plugin config for all request/connections */
69 typedef enum {
70 PROXY_FORWARDED_NONE = 0x00,
71 PROXY_FORWARDED_FOR = 0x01,
72 PROXY_FORWARDED_PROTO = 0x02,
73 PROXY_FORWARDED_HOST = 0x04,
74 PROXY_FORWARDED_BY = 0x08,
75 PROXY_FORWARDED_REMOTE_USER = 0x10
76 } proxy_forwarded_t;
78 struct sock_addr_mask {
79 sock_addr addr;
80 int bits;
83 struct sock_addr_masks {
84 struct sock_addr_mask *addrs;
85 size_t used;
86 size_t sz;
89 typedef struct {
90 array *forwarder;
91 struct sock_addr_masks *forward_masks;
92 array *headers;
93 array *opts_params;
94 unsigned int opts;
95 unsigned short int hap_PROXY;
96 unsigned short int hap_PROXY_ssl_client_verify;
97 short int forward_all;
98 } plugin_config;
100 typedef struct {
101 PLUGIN_DATA;
103 plugin_config **config_storage;
105 plugin_config conf;
106 } plugin_data;
108 static plugin_data *mod_extforward_plugin_data_singleton;
109 static int extforward_check_proxy;
112 /* context , used for restore remote ip */
114 typedef struct {
115 /* per-request state */
116 sock_addr saved_remote_addr;
117 buffer *saved_remote_addr_buf;
119 /* hap-PROXY protocol prior to receiving first request */
120 int(*saved_network_read)(server *, connection *, chunkqueue *, off_t);
122 /* connection-level state applied to requests in handle_request_env */
123 array *env;
124 int ssl_client_verify;
125 } handler_ctx;
128 static handler_ctx * handler_ctx_init(void) {
129 handler_ctx * hctx;
130 hctx = calloc(1, sizeof(*hctx));
131 return hctx;
134 static void handler_ctx_free(handler_ctx *hctx) {
135 free(hctx);
138 /* init the plugin data */
139 INIT_FUNC(mod_extforward_init) {
140 plugin_data *p;
141 p = calloc(1, sizeof(*p));
142 mod_extforward_plugin_data_singleton = p;
143 return p;
146 /* destroy the plugin data */
147 FREE_FUNC(mod_extforward_free) {
148 plugin_data *p = p_d;
150 UNUSED(srv);
152 if (!p) return HANDLER_GO_ON;
154 if (p->config_storage) {
155 size_t i;
157 for (i = 0; i < srv->config_context->used; i++) {
158 plugin_config *s = p->config_storage[i];
160 if (NULL == s) continue;
162 array_free(s->forwarder);
163 array_free(s->headers);
164 array_free(s->opts_params);
166 if (s->forward_masks) {
167 free(s->forward_masks->addrs);
168 free(s->forward_masks);
171 free(s);
173 free(p->config_storage);
177 free(p);
179 return HANDLER_GO_ON;
182 /* handle plugin config and check values */
184 SETDEFAULTS_FUNC(mod_extforward_set_defaults) {
185 plugin_data *p = p_d;
186 size_t i = 0;
188 config_values_t cv[] = {
189 { "extforward.forwarder", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
190 { "extforward.headers", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
191 { "extforward.params", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
192 { "extforward.hap-PROXY", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 3 */
193 { "extforward.hap-PROXY-ssl-client-verify", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 4 */
194 { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
197 if (!p) return HANDLER_ERROR;
199 p->config_storage = calloc(srv->config_context->used, sizeof(plugin_config *));
201 for (i = 0; i < srv->config_context->used; i++) {
202 data_config const* config = (data_config const*)srv->config_context->data[i];
203 plugin_config *s;
205 s = calloc(1, sizeof(plugin_config));
206 s->forwarder = array_init();
207 s->headers = array_init();
208 s->opts_params = array_init();
209 s->opts = PROXY_FORWARDED_NONE;
211 cv[0].destination = s->forwarder;
212 cv[1].destination = s->headers;
213 cv[2].destination = s->opts_params;
214 cv[3].destination = &s->hap_PROXY;
215 cv[4].destination = &s->hap_PROXY_ssl_client_verify;
217 p->config_storage[i] = s;
219 if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) {
220 return HANDLER_ERROR;
223 if (!array_is_kvstring(s->forwarder)) {
224 log_error_write(srv, __FILE__, __LINE__, "s",
225 "unexpected value for extforward.forwarder; expected list of \"IPaddr\" => \"trust\"");
226 return HANDLER_ERROR;
229 if (array_get_element(config->value, "extforward.forwarder")) {
230 const data_string * const allds = (data_string *)array_get_element(s->forwarder, "all");
231 s->forward_all = (NULL == allds) ? 0 : buffer_eq_icase_slen(allds->value, CONST_STR_LEN("trust")) ? 1 : -1;
232 for (size_t j = 0; j < s->forwarder->used; ++j) {
233 data_string * const ds = (data_string *)s->forwarder->data[j];
234 char * const nm_slash = strchr(ds->key->ptr, '/');
235 if (!buffer_eq_icase_slen(ds->value, CONST_STR_LEN("trust"))) {
236 if (!buffer_eq_icase_slen(ds->value, CONST_STR_LEN("untrusted"))) {
237 log_error_write(srv, __FILE__, __LINE__, "sbsbs", "ERROR: expect \"trust\", not \"", ds->key, "\" => \"", ds->value, "\"; treating as untrusted");
239 if (NULL != nm_slash) {
240 log_error_write(srv, __FILE__, __LINE__, "sbsbs", "ERROR: untrusted CIDR masks are ignored (\"", ds->key, "\" => \"", ds->value, "\")");
242 buffer_clear(ds->value); /* empty is untrusted */
243 continue;
245 if (NULL != nm_slash) {
246 struct sock_addr_mask *sm;
247 char *err;
248 const int nm_bits = strtol(nm_slash + 1, &err, 10);
249 int rc;
250 if (*err || nm_bits <= 0) {
251 log_error_write(srv, __FILE__, __LINE__, "sbs", "ERROR: invalid netmask:", ds->key, err);
252 return HANDLER_ERROR;
254 if (NULL == s->forward_masks) {
255 s->forward_masks = calloc(1, sizeof(struct sock_addr_masks));
256 force_assert(s->forward_masks);
258 if (s->forward_masks->used == s->forward_masks->sz) {
259 s->forward_masks->sz += 2;
260 s->forward_masks->addrs = realloc(s->forward_masks->addrs, s->forward_masks->sz * sizeof(struct sock_addr_mask));
261 force_assert(s->forward_masks->addrs);
263 sm = s->forward_masks->addrs + s->forward_masks->used++;
264 sm->bits = nm_bits;
265 *nm_slash = '\0';
266 rc = sock_addr_from_str_numeric(srv, &sm->addr, ds->key->ptr);
267 *nm_slash = '/';
268 if (1 != rc) return HANDLER_ERROR;
269 buffer_clear(ds->value); /* empty is untrusted, e.g. if subnet (incorrectly) appears in X-Forwarded-For */
274 if (!array_is_vlist(s->headers)) {
275 log_error_write(srv, __FILE__, __LINE__, "s",
276 "unexpected value for extforward.headers; expected list of \"headername\"");
277 return HANDLER_ERROR;
280 /* default to "X-Forwarded-For" or "Forwarded-For" if extforward.headers not specified or empty */
281 if (!s->hap_PROXY && 0 == s->headers->used && (0 == i || NULL != array_get_element(config->value, "extforward.headers"))) {
282 array_insert_value(s->headers, CONST_STR_LEN("X-Forwarded-For"));
283 array_insert_value(s->headers, CONST_STR_LEN("Forwarded-For"));
286 if (!array_is_kvany(s->opts_params)) {
287 log_error_write(srv, __FILE__, __LINE__, "s",
288 "unexpected value for extforward.params; expected ( \"param\" => \"value\" )");
289 return HANDLER_ERROR;
291 for (size_t j = 0, used = s->opts_params->used; j < used; ++j) {
292 proxy_forwarded_t param;
293 data_unset *du = s->opts_params->data[j];
294 #if 0 /*("for" and "proto" historical behavior: always enabled)*/
295 if (buffer_is_equal_string(du->key, CONST_STR_LEN("by"))) {
296 param = PROXY_FORWARDED_BY;
297 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("for"))) {
298 param = PROXY_FORWARDED_FOR;
299 } else
300 #endif
301 if (buffer_is_equal_string(du->key, CONST_STR_LEN("host"))) {
302 param = PROXY_FORWARDED_HOST;
303 #if 0
304 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("proto"))) {
305 param = PROXY_FORWARDED_PROTO;
306 #endif
307 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("remote_user"))) {
308 param = PROXY_FORWARDED_REMOTE_USER;
309 } else {
310 log_error_write(srv, __FILE__, __LINE__, "sb",
311 "extforward.params keys must be one of: host, remote_user, but not:", du->key);
312 return HANDLER_ERROR;
314 if (du->type == TYPE_STRING) {
315 data_string *ds = (data_string *)du;
316 if (buffer_is_equal_string(ds->value, CONST_STR_LEN("enable"))) {
317 s->opts |= param;
318 } else if (!buffer_is_equal_string(ds->value, CONST_STR_LEN("disable"))) {
319 log_error_write(srv, __FILE__, __LINE__, "sb",
320 "extforward.params values must be one of: 0, 1, enable, disable; error for key:", du->key);
321 return HANDLER_ERROR;
323 } else if (du->type == TYPE_INTEGER) {
324 data_integer *di = (data_integer *)du;
325 if (di->value) s->opts |= param;
326 } else {
327 log_error_write(srv, __FILE__, __LINE__, "sb",
328 "extforward.params values must be one of: 0, 1, enable, disable; error for key:", du->key);
329 return HANDLER_ERROR;
334 /* attempt to warn if mod_extforward is not last module loaded to hook
335 * handle_connection_accept. (Nice to have, but remove this check if
336 * it reaches too far into internals and prevents other code changes.)
337 * While it would be nice to check connection_handle_accept plugin slot
338 * to make sure mod_extforward is last, that info is private to plugin.c
339 * so merely warn if mod_openssl is loaded after mod_extforward, though
340 * future modules which hook connection_handle_accept might be missed.*/
341 for (i = 0; i < srv->config_context->used; ++i) {
342 plugin_config *s = p->config_storage[i];
343 if (s->hap_PROXY) {
344 size_t j;
345 for (j = 0; j < srv->srvconf.modules->used; ++j) {
346 data_string *ds = (data_string *)srv->srvconf.modules->data[j];
347 if (buffer_is_equal_string(ds->value, CONST_STR_LEN("mod_extforward"))) {
348 break;
351 for (; j < srv->srvconf.modules->used; ++j) {
352 data_string *ds = (data_string *)srv->srvconf.modules->data[j];
353 if (buffer_is_equal_string(ds->value, CONST_STR_LEN("mod_openssl"))) {
354 log_error_write(srv, __FILE__, __LINE__, "s",
355 "mod_extforward must be loaded after mod_openssl in server.modules when extforward.hap-PROXY = \"enable\"");
356 break;
359 break;
363 for (i = 0; i < srv->srvconf.modules->used; i++) {
364 data_string *ds = (data_string *)srv->srvconf.modules->data[i];
365 if (buffer_is_equal_string(ds->value, CONST_STR_LEN("mod_proxy"))) {
366 extforward_check_proxy = 1;
367 break;
371 return HANDLER_GO_ON;
374 #define PATCH(x) \
375 p->conf.x = s->x;
376 static int mod_extforward_patch_connection(server *srv, connection *con, plugin_data *p) {
377 size_t i, j;
378 plugin_config *s = p->config_storage[0];
380 PATCH(forwarder);
381 PATCH(forward_masks);
382 PATCH(headers);
383 PATCH(opts);
384 PATCH(hap_PROXY);
385 PATCH(hap_PROXY_ssl_client_verify);
386 PATCH(forward_all);
388 /* skip the first, the global context */
389 for (i = 1; i < srv->config_context->used; i++) {
390 data_config *dc = (data_config *)srv->config_context->data[i];
391 s = p->config_storage[i];
393 /* condition didn't match */
394 if (!config_check_cond(srv, con, dc)) continue;
396 /* merge config */
397 for (j = 0; j < dc->value->used; j++) {
398 data_unset *du = dc->value->data[j];
400 if (buffer_is_equal_string(du->key, CONST_STR_LEN("extforward.forwarder"))) {
401 PATCH(forwarder);
402 PATCH(forward_masks);
403 PATCH(forward_all);
404 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("extforward.headers"))) {
405 PATCH(headers);
406 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("extforward.params"))) {
407 PATCH(opts);
408 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("extforward.hap-PROXY"))) {
409 PATCH(hap_PROXY);
410 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("extforward.hap-PROXY-ssl-client-verify"))) {
411 PATCH(hap_PROXY_ssl_client_verify);
416 return 0;
418 #undef PATCH
422 extract a forward array from the environment
424 static array *extract_forward_array(buffer *pbuffer)
426 array *result = array_init();
427 if (!buffer_string_is_empty(pbuffer)) {
428 char *base, *curr;
429 /* state variable, 0 means not in string, 1 means in string */
430 int in_str = 0;
431 for (base = pbuffer->ptr, curr = pbuffer->ptr; *curr; curr++) {
432 if (in_str) {
433 if ((*curr > '9' || *curr < '0') && *curr != '.' && *curr != ':' && (*curr < 'a' || *curr > 'f') && (*curr < 'A' || *curr > 'F')) {
434 /* found an separator , insert value into result array */
435 array_insert_value(result, base, curr - base);
436 /* change state to not in string */
437 in_str = 0;
439 } else {
440 if ((*curr >= '0' && *curr <= '9') || *curr == ':' || (*curr >= 'a' && *curr <= 'f') || (*curr >= 'A' && *curr <= 'F')) {
441 /* found leading char of an IP address, move base pointer and change state */
442 base = curr;
443 in_str = 1;
447 /* if breaking out while in str, we got to the end of string, so add it */
448 if (in_str) {
449 array_insert_value(result, base, curr - base);
452 return result;
456 * check whether ip is trusted, return 1 for trusted , 0 for untrusted
458 static int is_proxy_trusted(plugin_data *p, const char * const ip, size_t iplen)
460 data_string *ds =
461 (data_string *)array_get_element_klen(p->conf.forwarder, ip, iplen);
462 if (NULL != ds) return !buffer_string_is_empty(ds->value);
464 if (p->conf.forward_masks) {
465 const struct sock_addr_mask * const addrs =p->conf.forward_masks->addrs;
466 const size_t aused = p->conf.forward_masks->used;
467 sock_addr addr;
468 /* C funcs inet_aton(), inet_pton() require '\0'-terminated IP str */
469 char addrstr[64]; /*(larger than INET_ADDRSTRLEN and INET6_ADDRSTRLEN)*/
470 if (iplen >= sizeof(addrstr)) return 0;
471 memcpy(addrstr, ip, iplen);
472 addrstr[iplen] = '\0';
474 if (1 != sock_addr_inet_pton(&addr, addrstr, AF_INET, 0)
475 && 1 != sock_addr_inet_pton(&addr, addrstr, AF_INET6, 0)) return 0;
477 for (size_t i = 0; i < aused; ++i) {
478 if (sock_addr_is_addr_eq_bits(&addr, &addrs[i].addr, addrs[i].bits))
479 return 1;
483 return 0;
486 static int is_connection_trusted(connection * const con, plugin_data *p)
488 if (p->conf.forward_all) return (1 == p->conf.forward_all);
489 return is_proxy_trusted(p, CONST_BUF_LEN(con->dst_addr_buf));
493 * Return last address of proxy that is not trusted.
494 * Do not accept "all" keyword here.
496 static const char *last_not_in_array(array *a, plugin_data *p)
498 int i;
500 for (i = a->used - 1; i >= 0; i--) {
501 data_string *ds = (data_string *)a->data[i];
502 if (!is_proxy_trusted(p, CONST_BUF_LEN(ds->value))) {
503 return ds->value->ptr;
506 return NULL;
509 static int mod_extforward_set_addr(server *srv, connection *con, plugin_data *p, const char *addr) {
510 sock_addr sock;
511 handler_ctx *hctx = con->plugin_ctx[p->id];
513 if (con->conf.log_request_handling) {
514 log_error_write(srv, __FILE__, __LINE__, "ss", "using address:", addr);
517 sock.plain.sa_family = AF_UNSPEC;
518 if (1 != sock_addr_from_str_numeric(srv, &sock, addr)) return 0;
519 if (sock.plain.sa_family == AF_UNSPEC) return 0;
521 /* we found the remote address, modify current connection and save the old address */
522 if (hctx) {
523 if (hctx->saved_remote_addr_buf) {
524 if (con->conf.log_request_handling) {
525 log_error_write(srv, __FILE__, __LINE__, "s",
526 "-- mod_extforward_uri_handler already patched this connection, resetting state");
528 con->dst_addr = hctx->saved_remote_addr;
529 buffer_free(con->dst_addr_buf);
530 con->dst_addr_buf = hctx->saved_remote_addr_buf;
531 hctx->saved_remote_addr_buf = NULL;
533 } else {
534 con->plugin_ctx[p->id] = hctx = handler_ctx_init();
536 /* save old address */
537 if (extforward_check_proxy) {
538 http_header_env_set(con, CONST_STR_LEN("_L_EXTFORWARD_ACTUAL_FOR"), CONST_BUF_LEN(con->dst_addr_buf));
540 hctx->saved_remote_addr = con->dst_addr;
541 hctx->saved_remote_addr_buf = con->dst_addr_buf;
542 /* patch connection address */
543 con->dst_addr = sock;
544 con->dst_addr_buf = buffer_init_string(addr);
546 if (con->conf.log_request_handling) {
547 log_error_write(srv, __FILE__, __LINE__, "ss",
548 "patching con->dst_addr_buf for the accesslog:", addr);
551 /* Now, clean the conf_cond cache, because we may have changed the results of tests */
552 config_cond_cache_reset_item(srv, con, COMP_HTTP_REMOTE_IP);
554 return 1;
557 static void mod_extforward_set_proto(server *srv, connection *con, const char *proto, size_t protolen) {
558 if (0 != protolen && !buffer_is_equal_caseless_string(con->uri.scheme, proto, protolen)) {
559 /* update scheme if X-Forwarded-Proto is set
560 * Limitations:
561 * - Only "http" or "https" are currently accepted since the request to lighttpd currently has to
562 * be HTTP/1.0 or HTTP/1.1 using http or https. If this is changed, then the scheme from this
563 * untrusted header must be checked to contain only alphanumeric characters, and to be a
564 * reasonable length, e.g. < 256 chars.
565 * - con->uri.scheme is not reset in mod_extforward_restore() but is currently not an issues since
566 * con->uri.scheme will be reset by next request. If a new module uses con->uri.scheme in the
567 * handle_request_done hook, then should evaluate if that module should use the forwarded value
568 * (probably) or the original value.
570 if (extforward_check_proxy) {
571 http_header_env_set(con, CONST_STR_LEN("_L_EXTFORWARD_ACTUAL_PROTO"), CONST_BUF_LEN(con->uri.scheme));
573 if (buffer_eq_icase_ss(proto, protolen, CONST_STR_LEN("https"))) {
574 buffer_copy_string_len(con->uri.scheme, CONST_STR_LEN("https"));
575 config_cond_cache_reset_item(srv, con, COMP_HTTP_SCHEME);
576 } else if (buffer_eq_icase_ss(proto, protolen, CONST_STR_LEN("http"))) {
577 buffer_copy_string_len(con->uri.scheme, CONST_STR_LEN("http"));
578 config_cond_cache_reset_item(srv, con, COMP_HTTP_SCHEME);
583 static handler_t mod_extforward_X_Forwarded_For(server *srv, connection *con, plugin_data *p, buffer *x_forwarded_for) {
584 /* build forward_array from forwarded data_string */
585 array *forward_array = extract_forward_array(x_forwarded_for);
586 const char *real_remote_addr = last_not_in_array(forward_array, p);
587 if (real_remote_addr != NULL) { /* parsed */
588 /* get scheme if X-Forwarded-Proto is set
589 * Limitations:
590 * - X-Forwarded-Proto may or may not be set by proxies, even if X-Forwarded-For is set
591 * - X-Forwarded-Proto may be a comma-separated list if there are multiple proxies,
592 * but the historical behavior of the code below only honored it if there was exactly one value
593 * (not done: walking backwards in X-Forwarded-Proto the same num of steps
594 * as in X-Forwarded-For to find proto set by last trusted proxy)
596 buffer *x_forwarded_proto = http_header_request_get(con, HTTP_HEADER_X_FORWARDED_PROTO, CONST_STR_LEN("X-Forwarded-Proto"));
597 if (mod_extforward_set_addr(srv, con, p, real_remote_addr) && NULL != x_forwarded_proto) {
598 mod_extforward_set_proto(srv, con, CONST_BUF_LEN(x_forwarded_proto));
601 array_free(forward_array);
602 return HANDLER_GO_ON;
605 static int find_end_quoted_string (const char * const s, int i) {
606 do {
607 ++i;
608 } while (s[i] != '"' && s[i] != '\0' && (s[i] != '\\' || s[++i] != '\0'));
609 return i;
612 static int find_next_semicolon_or_comma_or_eq (const char * const s, int i) {
613 for (; s[i] != '=' && s[i] != ';' && s[i] != ',' && s[i] != '\0'; ++i) {
614 if (s[i] == '"') {
615 i = find_end_quoted_string(s, i);
616 if (s[i] == '\0') return -1;
619 return i;
622 static int find_next_semicolon_or_comma (const char * const s, int i) {
623 for (; s[i] != ';' && s[i] != ',' && s[i] != '\0'; ++i) {
624 if (s[i] == '"') {
625 i = find_end_quoted_string(s, i);
626 if (s[i] == '\0') return -1;
629 return i;
632 static int buffer_backslash_unescape (buffer * const b) {
633 /* (future: might move to buffer.c) */
634 size_t j = 0;
635 size_t len = buffer_string_length(b);
636 char *p = memchr(b->ptr, '\\', len);
638 if (NULL == p) return 1; /*(nothing to do)*/
640 len -= (size_t)(p - b->ptr);
641 for (size_t i = 0; i < len; ++i) {
642 if (p[i] == '\\') {
643 if (++i == len) return 0; /*(invalid trailing backslash)*/
645 p[j++] = p[i];
647 buffer_string_set_length(b, (size_t)(p+j - b->ptr));
648 return 1;
651 static handler_t mod_extforward_Forwarded (server *srv, connection *con, plugin_data *p, buffer *forwarded) {
652 /* HTTP list need not consist of param=value tokens,
653 * but this routine expect such for HTTP Forwarded header
654 * Since info in each set of params is only used if from
655 * admin-specified trusted proxy:
656 * - invalid param=value tokens are ignored and skipped
657 * - not checking "for" exists in each set of params
658 * - not checking for duplicated params in each set of params
659 * - not checking canonical form of addr (also might be obfuscated)
660 * - obfuscated tokens permitted in chain, though end of trust is expected
661 * to be non-obfuscated IP for mod_extforward to masquerade as remote IP
662 * future: since (potentially) trusted proxies begin at end of string,
663 * it might be better to parse from end of string rather than parsing from
664 * beginning. Doing so would also allow reducing arbitrary param limit
665 * to number of params permitted per proxy.
667 char * const s = forwarded->ptr;
668 int i = 0, j = -1, v, vlen, k, klen;
669 int used = (int)buffer_string_length(forwarded);
670 int ofor = -1, oproto, ohost, oby, oremote_user;
671 int offsets[256];/*(~50 params is more than reasonably expected to handle)*/
672 while (i < used) {
673 while (s[i] == ' ' || s[i] == '\t') ++i;
674 if (s[i] == ';') { ++i; continue; }
675 if (s[i] == ',') {
676 if (j >= (int)(sizeof(offsets)/sizeof(int))) break;
677 offsets[++j] = -1; /*("offset" separating params from next proxy)*/
678 ++i;
679 continue;
681 if (s[i] == '\0') break;
683 k = i;
684 i = find_next_semicolon_or_comma_or_eq(s, i);
685 if (i < 0) {
686 /*(reject IP spoofing if attacker sets improper quoted-string)*/
687 log_error_write(srv, __FILE__, __LINE__, "s",
688 "invalid quoted-string in Forwarded header");
689 con->http_status = 400; /* Bad Request */
690 con->mode = DIRECT;
691 return HANDLER_FINISHED;
693 if (s[i] != '=') continue;
694 klen = i - k;
695 v = ++i;
696 i = find_next_semicolon_or_comma(s, i);
697 if (i < 0) {
698 /*(reject IP spoofing if attacker sets improper quoted-string)*/
699 log_error_write(srv, __FILE__, __LINE__, "s",
700 "invalid quoted-string in Forwarded header");
701 con->http_status = 400; /* Bad Request */
702 con->mode = DIRECT;
703 return HANDLER_FINISHED;
705 vlen = i - v; /* might be 0 */
707 /* have k, klen, v, vlen
708 * (might contain quoted string) (contents not validated or decoded)
709 * (might be repeated k)
711 if (0 == klen) continue; /* invalid k */
712 if (j >= (int)(sizeof(offsets)/sizeof(int))-4) break;
713 offsets[j+1] = k;
714 offsets[j+2] = klen;
715 offsets[j+3] = v;
716 offsets[j+4] = vlen;
717 j += 4;
720 if (j >= (int)(sizeof(offsets)/sizeof(int))-4) {
721 /* error processing Forwarded; too many params; fail closed */
722 log_error_write(srv, __FILE__, __LINE__, "s",
723 "Too many params in Forwarded header");
724 con->http_status = 400; /* Bad Request */
725 con->mode = DIRECT;
726 return HANDLER_FINISHED;
729 if (-1 == j) return HANDLER_GO_ON; /* make no changes */
730 used = j+1;
731 offsets[used] = -1; /* mark end of last set of params */
733 while (j >= 4) { /*(param=value pairs)*/
734 if (-1 == offsets[j]) { --j; continue; }
735 do {
736 j -= 3; /*(k, klen, v, vlen come in sets of 4)*/
737 } while ((3 != offsets[j+1] /* 3 == sizeof("for")-1 */
738 || !buffer_eq_icase_ssn(s+offsets[j], "for", 3))
739 && 0 != j-- && -1 != offsets[j]);
740 if (j < 0) break;
741 if (-1 == offsets[j]) { --j; continue; }
743 /* remove trailing spaces/tabs and double-quotes from string
744 * (note: not unescaping backslash escapes in quoted string) */
745 v = offsets[j+2];
746 vlen = v + offsets[j+3];
747 while (vlen > v && (s[vlen-1] == ' ' || s[vlen-1] == '\t')) --vlen;
748 if (vlen > v+1 && s[v] == '"' && s[vlen-1] == '"') {
749 offsets[j+2] = ++v;
750 --vlen;
751 if (s[v] == '[') {
752 /* remove "[]" surrounding IPv6, as well as (optional) port
753 * (assumes properly formatted IPv6 addr from trusted proxy) */
754 ++v;
755 do { --vlen; } while (vlen > v && s[vlen] != ']');
756 if (v == vlen) {
757 log_error_write(srv, __FILE__, __LINE__, "s",
758 "Invalid IPv6 addr in Forwarded header");
759 con->http_status = 400; /* Bad Request */
760 con->mode = DIRECT;
761 return HANDLER_FINISHED;
764 else if (s[v] != '_' && s[v] != '/' && s[v] != 'u') {
765 /* remove (optional) port from non-obfuscated IPv4 */
766 for (klen=vlen, vlen=v; vlen < klen && s[vlen] != ':'; ++vlen) ;
768 offsets[j+2] = v;
770 offsets[j+3] = vlen - v;
772 /* obfuscated ipstr and obfuscated port are also accepted here, as
773 * is path to unix domain socket, but note that backslash escapes
774 * in quoted-string were not unescaped above. Also, if obfuscated
775 * identifiers are rotated by proxies as recommended by RFC, then
776 * maintaining list of trusted identifiers is non-trivial and is not
777 * attempted by this module. */
779 if (v != vlen) {
780 int trusted = is_proxy_trusted(p, s+v, vlen-v);
782 if (s[v] != '_' && s[v] != '/'
783 && (7 != (vlen - v) || 0 != memcmp(s+v, "unknown", 7))) {
784 ofor = j; /* save most recent non-obfuscated ipstr */
787 if (!trusted) break;
790 do { --j; } while (j > 0 && -1 != offsets[j]);
791 if (j <= 0) break;
792 --j;
795 if (-1 != ofor) {
796 /* C funcs getaddrinfo(), inet_addr() require '\0'-terminated IP str */
797 char *ipend = s+offsets[ofor+2]+offsets[ofor+3];
798 char c = *ipend;
799 int rc;
800 *ipend = '\0';
801 rc = mod_extforward_set_addr(srv, con, p, s+offsets[ofor+2]);
802 *ipend = c;
803 if (!rc) return HANDLER_GO_ON; /* invalid addr; make no changes */
805 else {
806 return HANDLER_GO_ON; /* make no changes */
809 /* parse out params associated with for=<ip> addr set above */
810 oproto = ohost = oby = oremote_user = -1;
811 j = ofor;
812 if (j > 0) { do { --j; } while (j > 0 && -1 != offsets[j]); }
813 if (-1 == offsets[j]) ++j;
814 if (j == ofor) j += 4;
815 for (; -1 != offsets[j]; j+=4) { /*(k, klen, v, vlen come in sets of 4)*/
816 switch (offsets[j+1]) {
817 #if 0
818 case 2:
819 if (buffer_eq_icase_ssn(s+offsets[j], "by", 2))
820 oby = j;
821 break;
822 #endif
823 #if 0
824 /*(already handled above to find IP prior to earliest trusted proxy)*/
825 case 3:
826 if (buffer_eq_icase_ssn(s+offsets[j], "for", 3))
827 ofor = j;
828 break;
829 #endif
830 case 4:
831 if (buffer_eq_icase_ssn(s+offsets[j], "host", 4))
832 ohost = j;
833 break;
834 case 5:
835 if (buffer_eq_icase_ssn(s+offsets[j], "proto", 5))
836 oproto = j;
837 break;
838 case 11:
839 if (buffer_eq_icase_ssn(s+offsets[j], "remote_user", 11))
840 oremote_user = j;
841 break;
842 default:
843 break;
846 i = ++j;
848 if (-1 != oproto) {
849 /* remove trailing spaces/tabs, and double-quotes from proto
850 * (note: not unescaping backslash escapes in quoted string) */
851 v = offsets[oproto+2];
852 vlen = v + offsets[oproto+3];
853 while (vlen > v && (s[vlen-1] == ' ' || s[vlen-1] == '\t')) --vlen;
854 if (vlen > v+1 && s[v] == '"' && s[vlen-1] == '"') { ++v; --vlen; }
855 mod_extforward_set_proto(srv, con, s+v, vlen-v);
858 if (p->conf.opts & PROXY_FORWARDED_HOST) {
859 /* Limitations:
860 * - con->request.http_host is not reset in mod_extforward_restore()
861 * but is currently not an issues since con->request.http_host will be
862 * reset by next request. If a new module uses con->request.http_host
863 * in the handle_request_done hook, then should evaluate if that
864 * module should use the forwarded value (probably) or original value.
865 * - due to need to decode and unescape host=..., some extra work is
866 * done in the case where host matches current Host header.
867 * future: might add code to check if Host has actually changed or not
869 * note: change host after mod_extforward_set_proto() since that may
870 * affect scheme port used in http_request_host_policy() host
871 * normalization
874 /* find host param set by earliest trusted proxy in proxy chain
875 * (host might be changed anywhere along the chain) */
876 for (j = i; j < used && -1 == ohost; ) {
877 if (-1 == offsets[j]) { ++j; continue; }
878 if (4 == offsets[j+1]
879 && buffer_eq_icase_ssn(s+offsets[j], "host", 4))
880 ohost = j;
881 j += 4; /*(k, klen, v, vlen come in sets of 4)*/
883 if (-1 != ohost) {
884 if (extforward_check_proxy
885 && !buffer_string_is_empty(con->request.http_host)) {
886 http_header_env_set(con,
887 CONST_STR_LEN("_L_EXTFORWARD_ACTUAL_HOST"),
888 CONST_BUF_LEN(con->request.http_host));
890 /* remove trailing spaces/tabs, and double-quotes from host */
891 v = offsets[ohost+2];
892 vlen = v + offsets[ohost+3];
893 while (vlen > v && (s[vlen-1] == ' ' || s[vlen-1] == '\t')) --vlen;
894 if (vlen > v+1 && s[v] == '"' && s[vlen-1] == '"') {
895 ++v; --vlen;
896 buffer_copy_string_len(con->request.http_host, s+v, vlen-v);
897 if (!buffer_backslash_unescape(con->request.http_host)) {
898 log_error_write(srv, __FILE__, __LINE__, "s",
899 "invalid host= value in Forwarded header");
900 con->http_status = 400; /* Bad Request */
901 con->mode = DIRECT;
902 return HANDLER_FINISHED;
905 else {
906 buffer_copy_string_len(con->request.http_host, s+v, vlen-v);
909 if (0 != http_request_host_policy(con, con->request.http_host,
910 con->uri.scheme)) {
911 /*(reject invalid chars in Host)*/
912 log_error_write(srv, __FILE__, __LINE__, "s",
913 "invalid host= value in Forwarded header");
914 con->http_status = 400; /* Bad Request */
915 con->mode = DIRECT;
916 return HANDLER_FINISHED;
919 config_cond_cache_reset_item(srv, con, COMP_HTTP_HOST);
923 if (p->conf.opts & PROXY_FORWARDED_REMOTE_USER) {
924 /* find remote_user param set by closest proxy
925 * (auth may have been handled by any trusted proxy in proxy chain) */
926 for (j = i; j < used; ) {
927 if (-1 == offsets[j]) { ++j; continue; }
928 if (11 == offsets[j+1]
929 && buffer_eq_icase_ssn(s+offsets[j], "remote_user", 11))
930 oremote_user = j;
931 j += 4; /*(k, klen, v, vlen come in sets of 4)*/
933 if (-1 != oremote_user) {
934 /* ???: should we also support param for auth_type ??? */
935 /* remove trailing spaces/tabs, and double-quotes from remote_user*/
936 v = offsets[oremote_user+2];
937 vlen = v + offsets[oremote_user+3];
938 while (vlen > v && (s[vlen-1] == ' ' || s[vlen-1] == '\t')) --vlen;
939 if (vlen > v+1 && s[v] == '"' && s[vlen-1] == '"') {
940 buffer *euser;
941 ++v; --vlen;
942 http_header_env_set(con,
943 CONST_STR_LEN("REMOTE_USER"), s+v, vlen-v);
944 euser = http_header_env_get(con, CONST_STR_LEN("REMOTE_USER"));
945 force_assert(NULL != euser);
946 if (!buffer_backslash_unescape(euser)) {
947 log_error_write(srv, __FILE__, __LINE__, "s",
948 "invalid remote_user= value in Forwarded header");
949 con->http_status = 400; /* Bad Request */
950 con->mode = DIRECT;
951 return HANDLER_FINISHED;
954 else {
955 http_header_env_set(con,
956 CONST_STR_LEN("REMOTE_USER"), s+v, vlen-v);
961 #if 0
962 if ((p->conf.opts & PROXY_FORWARDED_CREATE_XFF)
963 && NULL == http_header_request_get(con, HTTP_HEADER_X_FORWARDED_FOR, CONST_STR_LEN("X-Forwarded-For"))) {
964 /* create X-Forwarded-For if not present
965 * (and at least original connecting IP is a trusted proxy) */
966 buffer *xff = srv->tmp_buf;
967 buffer_clear(xff);
968 for (j = 0; j < used; ) {
969 if (-1 == offsets[j]) { ++j; continue; }
970 if (3 == offsets[j+1]
971 && buffer_eq_icase_ssn(s+offsets[j], "for", 3)) {
972 if (!buffer_string_is_empty(xff))
973 buffer_append_string_len(xff, CONST_STR_LEN(", "));
974 /* quoted-string, IPv6 brackets, and :port already removed */
975 v = offsets[j+2];
976 vlen = offsets[j+3];
977 buffer_append_string_len(xff, s+v, vlen);
978 if (s[v-1] != '=') { /*(must have been quoted-string)*/
979 char *x =
980 memchr(xff->ptr+buffer_string_length(xff)-vlen,'\\',vlen);
981 if (NULL != x) { /* backslash unescape in-place */
982 for (v = 0; x[v]; ++x) {
983 if (x[v] == '\\' && x[++v] == '\0')
984 break; /*(invalid trailing backslash)*/
985 *x = x[v];
987 buffer_string_set_length(xff, x - xff->ptr);
990 /* skip to next group; take first "for=..." in group
991 * (should be 0 or 1 "for=..." per group, but not trusted) */
992 do { j += 4; } while (-1 != offsets[j]);
993 ++j;
994 continue;
996 j += 4; /*(k, klen, v, vlen come in sets of 4)*/
998 http_header_request_set(con, HTTP_HEADER_X_FORWARDED_FOR, CONST_STR_LEN("X-Forwarded-For"), CONST_BUF_LEN(xff));
1000 #endif
1002 return HANDLER_GO_ON;
1005 URIHANDLER_FUNC(mod_extforward_uri_handler) {
1006 plugin_data *p = p_d;
1007 buffer *forwarded = NULL;
1008 handler_ctx *hctx = con->plugin_ctx[p->id];
1009 int is_forwarded_header = 0;
1011 mod_extforward_patch_connection(srv, con, p);
1013 if (con->conf.log_request_handling) {
1014 log_error_write(srv, __FILE__, __LINE__, "s",
1015 "-- mod_extforward_uri_handler called");
1018 if (p->conf.hap_PROXY_ssl_client_verify) {
1019 data_string *ds;
1020 if (NULL != hctx && hctx->ssl_client_verify && NULL != hctx->env
1021 && NULL != (ds = (data_string *)array_get_element(hctx->env, "SSL_CLIENT_S_DN_CN"))) {
1022 http_header_env_set(con,
1023 CONST_STR_LEN("SSL_CLIENT_VERIFY"),
1024 CONST_STR_LEN("SUCCESS"));
1025 http_header_env_set(con,
1026 CONST_STR_LEN("REMOTE_USER"),
1027 CONST_BUF_LEN(ds->value));
1028 http_header_env_set(con,
1029 CONST_STR_LEN("AUTH_TYPE"),
1030 CONST_STR_LEN("SSL_CLIENT_VERIFY"));
1031 } else {
1032 http_header_env_set(con,
1033 CONST_STR_LEN("SSL_CLIENT_VERIFY"),
1034 CONST_STR_LEN("NONE"));
1038 for (size_t k = 0; k < p->conf.headers->used && NULL == forwarded; ++k) {
1039 buffer *hdr = ((data_string *)p->conf.headers->data[k])->value;
1040 forwarded = http_header_request_get(con, HTTP_HEADER_UNSPECIFIED, CONST_BUF_LEN(hdr));
1041 if (forwarded) {
1042 is_forwarded_header = buffer_is_equal_caseless_string(hdr, CONST_STR_LEN("Forwarded"));
1043 break;
1046 if (NULL == forwarded) {
1047 if (con->conf.log_request_handling) {
1048 log_error_write(srv, __FILE__, __LINE__, "s", "no forward header found, skipping");
1051 return HANDLER_GO_ON;
1054 /* if the remote ip itself is not trusted, then do nothing */
1055 if (!is_connection_trusted(con, p)) {
1056 if (con->conf.log_request_handling) {
1057 log_error_write(srv, __FILE__, __LINE__, "sbs",
1058 "remote address", con->dst_addr_buf, "is NOT a trusted proxy, skipping");
1061 return HANDLER_GO_ON;
1064 if (is_forwarded_header) {
1065 return mod_extforward_Forwarded(srv, con, p, forwarded);
1068 return mod_extforward_X_Forwarded_For(srv, con, p, forwarded);
1072 CONNECTION_FUNC(mod_extforward_handle_request_env) {
1073 plugin_data *p = p_d;
1074 handler_ctx *hctx = con->plugin_ctx[p->id];
1075 UNUSED(srv);
1076 if (NULL == hctx || NULL == hctx->env) return HANDLER_GO_ON;
1077 for (size_t i=0; i < hctx->env->used; ++i) {
1078 /* note: replaces values which may have been set by mod_openssl
1079 * (when mod_extforward is listed after mod_openssl in server.modules)*/
1080 data_string *ds = (data_string *)hctx->env->data[i];
1081 http_header_env_set(con,
1082 CONST_BUF_LEN(ds->key), CONST_BUF_LEN(ds->value));
1084 return HANDLER_GO_ON;
1088 CONNECTION_FUNC(mod_extforward_restore) {
1089 plugin_data *p = p_d;
1090 handler_ctx *hctx = con->plugin_ctx[p->id];
1092 if (!hctx) return HANDLER_GO_ON;
1094 if (NULL != hctx->saved_network_read) {
1095 con->network_read = hctx->saved_network_read;
1096 hctx->saved_network_read = NULL;
1099 if (NULL != hctx->saved_remote_addr_buf) {
1100 con->dst_addr = hctx->saved_remote_addr;
1101 buffer_free(con->dst_addr_buf);
1102 con->dst_addr_buf = hctx->saved_remote_addr_buf;
1103 hctx->saved_remote_addr_buf = NULL;
1104 /* Now, clean the conf_cond cache, because we may have changed the results of tests */
1105 config_cond_cache_reset_item(srv, con, COMP_HTTP_REMOTE_IP);
1108 if (NULL == hctx->env) {
1109 handler_ctx_free(hctx);
1110 con->plugin_ctx[p->id] = NULL;
1113 return HANDLER_GO_ON;
1117 CONNECTION_FUNC(mod_extforward_handle_con_close)
1119 plugin_data *p = p_d;
1120 handler_ctx *hctx = con->plugin_ctx[p->id];
1121 UNUSED(srv);
1122 if (NULL != hctx) {
1123 if (NULL != hctx->saved_network_read) {
1124 con->network_read = hctx->saved_network_read;
1126 if (NULL != hctx->saved_remote_addr_buf) {
1127 con->dst_addr = hctx->saved_remote_addr;
1128 buffer_free(con->dst_addr_buf);
1129 con->dst_addr_buf = hctx->saved_remote_addr_buf;
1131 if (NULL != hctx->env) {
1132 array_free(hctx->env);
1134 handler_ctx_free(hctx);
1135 con->plugin_ctx[p->id] = NULL;
1138 return HANDLER_GO_ON;
1142 static int mod_extforward_network_read (server *srv, connection *con, chunkqueue *cq, off_t max_bytes);
1144 CONNECTION_FUNC(mod_extforward_handle_con_accept)
1146 plugin_data *p = p_d;
1147 mod_extforward_patch_connection(srv, con, p);
1148 if (!p->conf.hap_PROXY) return HANDLER_GO_ON;
1149 if (is_connection_trusted(con, p)) {
1150 handler_ctx *hctx = handler_ctx_init();
1151 con->plugin_ctx[p->id] = hctx;
1152 hctx->saved_network_read = con->network_read;
1153 con->network_read = mod_extforward_network_read;
1155 else {
1156 if (con->conf.log_request_handling) {
1157 log_error_write(srv, __FILE__, __LINE__, "sbs",
1158 "remote address", con->dst_addr_buf,
1159 "is NOT a trusted proxy, skipping");
1162 return HANDLER_GO_ON;
1166 /* this function is called at dlopen() time and inits the callbacks */
1168 int mod_extforward_plugin_init(plugin *p);
1169 int mod_extforward_plugin_init(plugin *p) {
1170 p->version = LIGHTTPD_VERSION_ID;
1171 p->name = buffer_init_string("extforward");
1173 p->init = mod_extforward_init;
1174 p->handle_connection_accept = mod_extforward_handle_con_accept;
1175 p->handle_uri_raw = mod_extforward_uri_handler;
1176 p->handle_request_env = mod_extforward_handle_request_env;
1177 p->handle_request_done = mod_extforward_restore;
1178 p->connection_reset = mod_extforward_restore;
1179 p->handle_connection_close = mod_extforward_handle_con_close;
1180 p->set_defaults = mod_extforward_set_defaults;
1181 p->cleanup = mod_extforward_free;
1183 p->data = NULL;
1185 return 0;
1191 /* Modified from:
1192 * http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
1194 9. Sample code
1196 The code below is an example of how a receiver may deal with both versions of
1197 the protocol header for TCP over IPv4 or IPv6. The function is supposed to be
1198 called upon a read event. Addresses may be directly copied into their final
1199 memory location since they're transported in network byte order. The sending
1200 side is even simpler and can easily be deduced from this sample code.
1204 union hap_PROXY_hdr {
1205 struct {
1206 char line[108];
1207 } v1;
1208 struct {
1209 uint8_t sig[12];
1210 uint8_t ver_cmd;
1211 uint8_t fam;
1212 uint16_t len;
1213 union {
1214 struct { /* for TCP/UDP over IPv4, len = 12 */
1215 uint32_t src_addr;
1216 uint32_t dst_addr;
1217 uint16_t src_port;
1218 uint16_t dst_port;
1219 } ip4;
1220 struct { /* for TCP/UDP over IPv6, len = 36 */
1221 uint8_t src_addr[16];
1222 uint8_t dst_addr[16];
1223 uint16_t src_port;
1224 uint16_t dst_port;
1225 } ip6;
1226 struct { /* for AF_UNIX sockets, len = 216 */
1227 uint8_t src_addr[108];
1228 uint8_t dst_addr[108];
1229 } unx;
1230 } addr;
1231 } v2;
1235 If the length specified in the PROXY protocol header indicates that additional
1236 bytes are part of the header beyond the address information, a receiver may
1237 choose to skip over and ignore those bytes, or attempt to interpret those
1238 bytes.
1240 The information in those bytes will be arranged in Type-Length-Value (TLV
1241 vectors) in the following format. The first byte is the Type of the vector.
1242 The second two bytes represent the length in bytes of the value (not included
1243 the Type and Length bytes), and following the length field is the number of
1244 bytes specified by the length.
1246 struct pp2_tlv {
1247 uint8_t type;
1248 uint8_t length_hi;
1249 uint8_t length_lo;
1250 /*uint8_t value[0];*//* C99 zero-length array */
1254 The following types have already been registered for the <type> field :
1257 #define PP2_TYPE_ALPN 0x01
1258 #define PP2_TYPE_AUTHORITY 0x02
1259 #define PP2_TYPE_CRC32C 0x03
1260 #define PP2_TYPE_NOOP 0x04
1261 #define PP2_TYPE_SSL 0x20
1262 #define PP2_SUBTYPE_SSL_VERSION 0x21
1263 #define PP2_SUBTYPE_SSL_CN 0x22
1264 #define PP2_SUBTYPE_SSL_CIPHER 0x23
1265 #define PP2_SUBTYPE_SSL_SIG_ALG 0x24
1266 #define PP2_SUBTYPE_SSL_KEY_ALG 0x25
1267 #define PP2_TYPE_NETNS 0x30
1270 For the type PP2_TYPE_SSL, the value is itselv a defined like this :
1273 struct pp2_tlv_ssl {
1274 uint8_t client;
1275 uint32_t verify;
1276 /*struct pp2_tlv sub_tlv[0];*//* C99 zero-length array */
1280 And the <client> field is made of a bit field from the following values,
1281 indicating which element is present :
1284 #define PP2_CLIENT_SSL 0x01
1285 #define PP2_CLIENT_CERT_CONN 0x02
1286 #define PP2_CLIENT_CERT_SESS 0x04
1291 #ifndef MSG_DONTWAIT
1292 #define MSG_DONTWAIT 0
1293 #endif
1294 #ifndef MSG_NOSIGNAL
1295 #define MSG_NOSIGNAL 0
1296 #endif
1298 /* returns 0 if needs to poll, <0 upon error or >0 is protocol vers (success) */
1299 static int hap_PROXY_recv (const int fd, union hap_PROXY_hdr * const hdr, const int family, const int so_type)
1301 static const char v2sig[12] =
1302 "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A";
1304 ssize_t ret;
1305 size_t sz;
1306 int ver;
1308 do {
1309 ret = recv(fd, hdr, sizeof(*hdr), MSG_PEEK|MSG_DONTWAIT|MSG_NOSIGNAL);
1310 } while (-1 == ret && errno == EINTR);
1312 if (-1 == ret)
1313 return (errno == EAGAIN
1314 #ifdef EWOULDBLOCK
1315 #if EAGAIN != EWOULDBLOCK
1316 || errno == EWOULDBLOCK
1317 #endif
1318 #endif
1319 ) ? 0 : -1;
1321 if (ret >= 16 && 0 == memcmp(&hdr->v2, v2sig, 12)
1322 && (hdr->v2.ver_cmd & 0xF0) == 0x20) {
1323 ver = 2;
1324 sz = 16 + (size_t)ntohs(hdr->v2.len);
1325 if ((size_t)ret < sz)
1326 return -2; /* truncated or too large header */
1328 switch (hdr->v2.ver_cmd & 0xF) {
1329 case 0x01: break; /* PROXY command */
1330 case 0x00: break; /* LOCAL command */
1331 default: return -2; /* not a supported command */
1334 else if (ret >= 8 && 0 == memcmp(hdr->v1.line, "PROXY", 5)) {
1335 const char *end = memchr(hdr->v1.line, '\r', ret - 1);
1336 if (!end || end[1] != '\n')
1337 return -2; /* partial or invalid header */
1338 ver = 1;
1339 sz = (size_t)(end + 2 - hdr->v1.line); /* skip header + CRLF */
1341 else {
1342 /* Wrong protocol */
1343 return -2;
1346 /* we need to consume the appropriate amount of data from the socket
1347 * (overwrites existing contents of hdr with same data) */
1348 UNUSED(family);
1349 UNUSED(so_type);
1350 do {
1351 #if defined(MSG_TRUNC) && defined(__linux__)
1352 if ((family==AF_INET || family==AF_INET6) && so_type == SOCK_STREAM) {
1353 ret = recv(fd, hdr, sz, MSG_TRUNC|MSG_DONTWAIT|MSG_NOSIGNAL);
1354 if (ret >= 0 || errno != EINVAL) continue;
1356 #endif
1357 ret = recv(fd, hdr, sz, MSG_DONTWAIT|MSG_NOSIGNAL);
1358 } while (-1 == ret && errno == EINTR);
1359 if (ret < 0) return -1;
1360 if (ret != (ssize_t)sz) {
1361 errno = EIO; /*(partial read; valid but unexpected; not handled)*/
1362 return -1;
1364 if (1 == ver) hdr->v1.line[sz-2] = '\0'; /*terminate str to ease parsing*/
1365 return ver;
1369 static int mod_extforward_hap_PROXY_v1 (connection * const con,
1370 union hap_PROXY_hdr * const hdr)
1372 #ifdef __COVERITY__
1373 __coverity_tainted_data_sink__(hdr);
1374 #endif
1376 /* samples
1377 * "PROXY TCP4 255.255.255.255 255.255.255.255 65535 65535\r\n"
1378 * "PROXY TCP6 ffff:f...f:ffff ffff:f...f:ffff 65535 65535\r\n"
1379 * "PROXY UNKNOWN\r\n"
1380 * "PROXY UNKNOWN ffff:f...f:ffff ffff:f...f:ffff 65535 65535\r\n"
1382 char *s = hdr->v1.line + sizeof("PROXY")-1; /*checked in hap_PROXY_recv()*/
1383 char *src_addr, *dst_addr, *src_port, *dst_port, *e;
1384 int family;
1385 long src_lport, dst_lport;
1386 if (*s != ' ') return -1;
1387 ++s;
1388 if (s[0] == 'T' && s[1] == 'C' && s[2] == 'P' && s[4] == ' ') {
1389 if (s[3] == '4') {
1390 family = AF_INET;
1391 } else if (s[3] == '6') {
1392 family = AF_INET6;
1394 else {
1395 return -1;
1397 s += 5;
1399 else if (0 == memcmp(s, "UNKNOWN", sizeof("UNKNOWN")-1)
1400 && (s[7] == '\0' || s[7] == ' ')) {
1401 return 0; /* keep local connection address */
1403 else {
1404 return -1;
1407 /*(strsep() should be fairly portable, but is not standard)*/
1408 src_addr = s;
1409 dst_addr = strchr(src_addr, ' ');
1410 if (NULL == dst_addr) return -1;
1411 *dst_addr++ = '\0';
1412 src_port = strchr(dst_addr, ' ');
1413 if (NULL == src_port) return -1;
1414 *src_port++ = '\0';
1415 dst_port = strchr(src_port, ' ');
1416 if (NULL == dst_port) return -1;
1417 *dst_port++ = '\0';
1419 src_lport = strtol(src_port, &e, 10);
1420 if (src_lport <= 0 || src_lport > USHRT_MAX || *e != '\0') return -1;
1421 dst_lport = strtol(dst_port, &e, 10);
1422 if (dst_lport <= 0 || dst_lport > USHRT_MAX || *e != '\0') return -1;
1424 if (1 != sock_addr_inet_pton(&con->dst_addr,
1425 src_addr, family, (unsigned short)src_lport))
1426 return -1;
1427 /* Forwarded by=... could be saved here.
1428 * (see additional comments in mod_extforward_hap_PROXY_v2()) */
1430 /* re-parse addr to string to normalize
1431 * (instead of trusting PROXY to provide canonicalized src_addr string)
1432 * (should prefer PROXY v2 protocol if concerned about performance) */
1433 sock_addr_inet_ntop_copy_buffer(con->dst_addr_buf, &con->dst_addr);
1435 return 0;
1439 static int mod_extforward_hap_PROXY_v2 (connection * const con,
1440 union hap_PROXY_hdr * const hdr)
1442 #ifdef __COVERITY__
1443 __coverity_tainted_data_sink__(hdr);
1444 #endif
1446 /* If HAProxy-PROXY protocol used, then lighttpd acts as transparent proxy,
1447 * masquerading as servicing the client IP provided in by HAProxy-PROXY hdr.
1448 * The connecting con->dst_addr and con->dst_addr_buf are not saved here,
1449 * so that info is lost unless getsockname() and getpeername() are used.
1450 * One result is that mod_proxy will use the masqueraded IP instead of the
1451 * actual IP when updated Forwarded and X-Forwarded-For (but if actual
1452 * connection IPs needed, better to save the info here rather than use
1453 * syscalls to retrieve the info later).
1454 * (Exception: con->dst_addr can be further changed if mod_extforward parses
1455 * Forwaded or X-Forwarded-For request headers later, after request headers
1456 * have been received.)
1459 /* Forwarded by=... could be saved here. The by param is for backends to be
1460 * able to construct URIs for that interface (interface on server which
1461 * received request and made PROXY connection here), though that server
1462 * should provide that information in updated Forwarded or X-Forwarded-For
1463 * HTTP headers */
1464 /*struct sockaddr_storage by;*/
1466 /* Addresses provided by HAProxy-PROXY protocol are in network byte order.
1467 * Note: addr info is not validated, so do not accept HAProxy-PROXY
1468 * protocol from untrusted servers. For example, untrusted servers from
1469 * which HAProxy-PROXY protocol is accepted (don't do that) could pretend
1470 * to be from the internal network and might thereby bypass security policy.
1473 /* (Clear con->dst_addr with memset() in case actual and proxies IPs
1474 * are different domains, e.g. one is IPv4 and the other is IPv6) */
1476 struct pp2_tlv *tlv;
1477 uint32_t sz = ntohs(hdr->v2.len);
1478 uint32_t len = 0;
1480 switch (hdr->v2.ver_cmd & 0xF) {
1481 case 0x01: break; /* PROXY command */
1482 case 0x00: return 0;/* LOCAL command; keep local connection address */
1483 default: return -1;/* should not happen; validated in hap_PROXY_recv()*/
1486 /* PROXY command */
1488 switch (hdr->v2.fam) {
1489 case 0x11: /* TCPv4 */
1490 sock_addr_assign(&con->dst_addr, AF_INET, hdr->v2.addr.ip4.src_port,
1491 &hdr->v2.addr.ip4.src_addr);
1492 sock_addr_inet_ntop_copy_buffer(con->dst_addr_buf, &con->dst_addr);
1493 #if 0
1494 ((struct sockaddr_in *)&by)->sin_family = AF_INET;
1495 ((struct sockaddr_in *)&by)->sin_addr.s_addr =
1496 hdr->v2.addr.ip4.dst_addr;
1497 ((struct sockaddr_in *)&by)->sin_port =
1498 hdr->v2.addr.ip4.dst_port;
1499 #endif
1500 len = (uint32_t)sizeof(hdr->v2.addr.ip4);
1501 break;
1502 #ifdef HAVE_IPV6
1503 case 0x21: /* TCPv6 */
1504 sock_addr_assign(&con->dst_addr, AF_INET6, hdr->v2.addr.ip6.src_port,
1505 &hdr->v2.addr.ip6.src_addr);
1506 sock_addr_inet_ntop_copy_buffer(con->dst_addr_buf, &con->dst_addr);
1507 #if 0
1508 ((struct sockaddr_in6 *)&by)->sin6_family = AF_INET6;
1509 memcpy(&((struct sockaddr_in6 *)&by)->sin6_addr,
1510 hdr->v2.addr.ip6.dst_addr, 16);
1511 ((struct sockaddr_in6 *)&by)->sin6_port =
1512 hdr->v2.addr.ip6.dst_port;
1513 #endif
1514 len = (uint32_t)sizeof(hdr->v2.addr.ip6);
1515 break;
1516 #endif
1517 #ifdef HAVE_SYS_UN_H
1518 case 0x31: /* UNIX domain socket */
1520 char *src_addr = (char *)hdr->v2.addr.unx.src_addr;
1521 char *z = memchr(src_addr, '\0', UNIX_PATH_MAX);
1522 if (NULL == z) return -1; /* invalid addr; too long */
1523 len = (uint32_t)(z - src_addr + 1); /*(+1 for '\0')*/
1524 sock_addr_assign(&con->dst_addr, AF_UNIX, 0, src_addr);
1525 buffer_copy_string_len(con->dst_addr_buf, src_addr, len);
1527 #if 0 /*(dst_addr should be identical to src_addr for AF_UNIX)*/
1528 ((struct sockaddr_un *)&by)->sun_family = AF_UNIX;
1529 memcpy(&((struct sockaddr_un *)&by)->sun_path,
1530 hdr->v2.addr.unx.dst_addr, 108);
1531 #endif
1532 len = (uint32_t)sizeof(hdr->v2.addr.unx);
1533 break;
1534 #endif
1535 default: /* keep local connection address; unsupported protocol */
1536 return 0;
1539 /* (optional) Type-Length-Value (TLV vectors) follow addresses */
1541 tlv = (struct pp2_tlv *)((char *)hdr + 16);
1542 for (sz -= len, len -= 3; sz >= 3; sz -= 3 + len) {
1543 tlv = (struct pp2_tlv *)((char *)tlv + 3 + len);
1544 len = ((uint32_t)tlv->length_hi << 8) | tlv->length_lo;
1545 if (3 + len > sz) break; /*(invalid TLV)*/
1546 switch (tlv->type) {
1547 #if 0 /*(not implemented here)*/
1548 case PP2_TYPE_ALPN:
1549 case PP2_TYPE_AUTHORITY:
1550 case PP2_TYPE_CRC32C:
1551 #endif
1552 case PP2_TYPE_SSL: {
1553 static const uint32_t zero = 0;
1554 handler_ctx *hctx =
1555 con->plugin_ctx[mod_extforward_plugin_data_singleton->id];
1556 struct pp2_tlv_ssl *tlv_ssl =
1557 (struct pp2_tlv_ssl *)(void *)((char *)tlv+3);
1558 struct pp2_tlv *subtlv = tlv;
1559 if (tlv_ssl->client & PP2_CLIENT_SSL) {
1560 buffer_copy_string_len(con->proto, CONST_STR_LEN("https"));
1562 if ((tlv_ssl->client & (PP2_CLIENT_CERT_CONN|PP2_CLIENT_CERT_SESS))
1563 && 0 == memcmp(&tlv_ssl->verify, &zero, 4)) { /* misaligned */
1564 hctx->ssl_client_verify = 1;
1566 for (uint32_t subsz = len-5, n = 5; subsz >= 3; subsz -= 3 + n) {
1567 subtlv = (struct pp2_tlv *)((char *)subtlv + 3 + n);
1568 n = ((uint32_t)subtlv->length_hi << 8) | subtlv->length_lo;
1569 if (3 + n > subsz) break; /*(invalid TLV)*/
1570 if (NULL == hctx->env) hctx->env = array_init();
1571 switch (subtlv->type) {
1572 case PP2_SUBTYPE_SSL_VERSION:
1573 array_set_key_value(hctx->env,
1574 CONST_STR_LEN("SSL_PROTOCOL"),
1575 (char *)subtlv+3, n);
1576 break;
1577 case PP2_SUBTYPE_SSL_CN:
1578 /* (tlv_ssl->client & PP2_CLIENT_CERT_CONN)
1579 * or
1580 * (tlv_ssl->client & PP2_CLIENT_CERT_SESS) */
1581 array_set_key_value(hctx->env,
1582 CONST_STR_LEN("SSL_CLIENT_S_DN_CN"),
1583 (char *)subtlv+3, n);
1584 break;
1585 case PP2_SUBTYPE_SSL_CIPHER:
1586 array_set_key_value(hctx->env,
1587 CONST_STR_LEN("SSL_CIPHER"),
1588 (char *)subtlv+3, n);
1589 break;
1590 case PP2_SUBTYPE_SSL_SIG_ALG:
1591 array_set_key_value(hctx->env,
1592 CONST_STR_LEN("SSL_SERVER_A_SIG"),
1593 (char *)subtlv+3, n);
1594 break;
1595 case PP2_SUBTYPE_SSL_KEY_ALG:
1596 array_set_key_value(hctx->env,
1597 CONST_STR_LEN("SSL_SERVER_A_KEY"),
1598 (char *)subtlv+3, n);
1599 break;
1600 default:
1601 break;
1604 break;
1606 #if 0 /*(not implemented here)*/
1607 case PP2_TYPE_NETNS:
1608 #endif
1609 /*case PP2_TYPE_NOOP:*//* no-op */
1610 default:
1611 break;
1615 return 0;
1619 static int mod_extforward_network_read (server *srv, connection *con,
1620 chunkqueue *cq, off_t max_bytes)
1622 /* XXX: when using hap-PROXY protocol, currently avoid overhead of setting
1623 * _L_ environment variables for mod_proxy to accurately set Forwarded hdr
1624 * In the future, might add config switch to enable doing this extra work */
1626 union hap_PROXY_hdr hdr;
1627 int rc = hap_PROXY_recv(con->fd, &hdr,
1628 con->dst_addr.plain.sa_family, SOCK_STREAM);
1629 switch (rc) {
1630 case 2: rc = mod_extforward_hap_PROXY_v2(con, &hdr); break;
1631 case 1: rc = mod_extforward_hap_PROXY_v1(con, &hdr); break;
1632 case 0: return 0; /*(errno == EAGAIN || errno == EWOULDBLOCK)*/
1633 case -1: log_error_write(srv, __FILE__, __LINE__, "ss",
1634 "hap-PROXY recv()", strerror(errno));
1635 rc = -1; break;
1636 case -2: log_error_write(srv, __FILE__, __LINE__, "s",
1637 "hap-PROXY proto received "
1638 "invalid/unsupported request");
1639 /* fall through */
1640 default: rc = -1; break;
1643 mod_extforward_restore(srv, con, mod_extforward_plugin_data_singleton);
1644 return (0 == rc) ? con->network_read(srv, con, cq, max_bytes) : rc;