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