xref: /lighttpd1.4/src/mod_extforward.c (revision 49e9f0ac)
1 #include "first.h"
2 
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"
9 
10 #include "plugin.h"
11 
12 #include "configfile.h"
13 
14 #include <limits.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <errno.h>
18 
19 #include "sys-socket.h"
20 
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 [email protected], #1528
25  *
26  * Config example:
27  *
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" )
31  *
32  *       Trust all proxies  (NOT RECOMMENDED!)
33  *       extforward.forwarder = ( "all" => "trust")
34  *
35  *       Note that "all" has precedence over specific entries,
36  *       so "all except" setups will not work.
37  *
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.
41  *
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).
49  *
50  * Tested behaviours:
51  *
52  *  mod_access: Will match on the real client.
53  *
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:
58  *
59  *    server.modules  = (
60  *       .....
61  *       mod_accesslog,
62  *       mod_extforward
63  *    )
64  */
65 
66 
67 /* plugin config for all request/connections */
68 
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;
77 
78 struct sock_addr_mask {
79   sock_addr addr;
80   int bits;
81 };
82 
83 struct sock_addr_masks {
84   struct sock_addr_mask *addrs;
85   size_t used;
86   size_t sz;
87 };
88 
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;
99 
100 typedef struct {
101 	PLUGIN_DATA;
102 
103 	plugin_config **config_storage;
104 
105 	plugin_config conf;
106 } plugin_data;
107 
108 static plugin_data *mod_extforward_plugin_data_singleton;
109 static int extforward_check_proxy;
110 
111 
112 /* context , used for restore remote ip */
113 
114 typedef struct {
115 	/* per-request state */
116 	sock_addr saved_remote_addr;
117 	buffer *saved_remote_addr_buf;
118 
119 	/* hap-PROXY protocol prior to receiving first request */
120 	int(*saved_network_read)(server *, connection *, chunkqueue *, off_t);
121 
122 	/* connection-level state applied to requests in handle_request_env */
123 	array *env;
124 	int ssl_client_verify;
125 } handler_ctx;
126 
127 
128 static handler_ctx * handler_ctx_init(void) {
129 	handler_ctx * hctx;
130 	hctx = calloc(1, sizeof(*hctx));
131 	return hctx;
132 }
133 
134 static void handler_ctx_free(handler_ctx *hctx) {
135 	free(hctx);
136 }
137 
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;
144 }
145 
146 /* destroy the plugin data */
147 FREE_FUNC(mod_extforward_free) {
148 	plugin_data *p = p_d;
149 
150 	UNUSED(srv);
151 
152 	if (!p) return HANDLER_GO_ON;
153 
154 	if (p->config_storage) {
155 		size_t i;
156 
157 		for (i = 0; i < srv->config_context->used; i++) {
158 			plugin_config *s = p->config_storage[i];
159 
160 			if (NULL == s) continue;
161 
162 			array_free(s->forwarder);
163 			array_free(s->headers);
164 			array_free(s->opts_params);
165 
166 			if (s->forward_masks) {
167 				free(s->forward_masks->addrs);
168 				free(s->forward_masks);
169 			}
170 
171 			free(s);
172 		}
173 		free(p->config_storage);
174 	}
175 
176 
177 	free(p);
178 
179 	return HANDLER_GO_ON;
180 }
181 
182 /* handle plugin config and check values */
183 
184 SETDEFAULTS_FUNC(mod_extforward_set_defaults) {
185 	plugin_data *p = p_d;
186 	size_t i = 0;
187 
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 }
195 	};
196 
197 	if (!p) return HANDLER_ERROR;
198 
199 	p->config_storage = calloc(srv->config_context->used, sizeof(plugin_config *));
200 
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;
204 
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;
210 
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;
216 
217 		p->config_storage[i] = s;
218 
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;
221 		}
222 
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;
227 		}
228 
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 : (0 == strcasecmp(allds->value->ptr, "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 (0 != strcasecmp(ds->value->ptr, "trust")) {
236 					if (0 != strcasecmp(ds->value->ptr, "untrusted")) {
237 						log_error_write(srv, __FILE__, __LINE__, "sbsbs", "ERROR: expect \"trust\", not \"", ds->key, "\" => \"", ds->value, "\"; treating as untrusted");
238 					}
239 					if (NULL != nm_slash) {
240 						log_error_write(srv, __FILE__, __LINE__, "sbsbs", "ERROR: untrusted CIDR masks are ignored (\"", ds->key, "\" => \"", ds->value, "\")");
241 					}
242 					buffer_clear(ds->value); /* empty is untrusted */
243 					continue;
244 				}
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;
253 					}
254 					if (NULL == s->forward_masks) {
255 						s->forward_masks = calloc(1, sizeof(struct sock_addr_masks));
256 						force_assert(s->forward_masks);
257 					}
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);
262 					}
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 */
270 				}
271 			}
272 		}
273 
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;
278 		}
279 
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"));
284 		}
285 
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;
290 		}
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;
313 			}
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;
322 				}
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;
330 			}
331 		}
332 	}
333 
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;
349 				}
350 			}
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;
357 				}
358 			}
359 			break;
360 		}
361 	}
362 
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;
368 		}
369 	}
370 
371 	return HANDLER_GO_ON;
372 }
373 
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];
379 
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);
387 
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];
392 
393 		/* condition didn't match */
394 		if (!config_check_cond(srv, con, dc)) continue;
395 
396 		/* merge config */
397 		for (j = 0; j < dc->value->used; j++) {
398 			data_unset *du = dc->value->data[j];
399 
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);
412 			}
413 		}
414 	}
415 
416 	return 0;
417 }
418 #undef PATCH
419 
420 
421 /*
422    extract a forward array from the environment
423 */
424 static array *extract_forward_array(buffer *pbuffer)
425 {
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;
438 				}
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;
444 				}
445 			}
446 		}
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);
450 		}
451 	}
452 	return result;
453 }
454 
455 /*
456  * check whether ip is trusted, return 1 for trusted , 0 for untrusted
457  */
458 static int is_proxy_trusted(plugin_data *p, const char * const ip, size_t iplen)
459 {
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);
463 
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';
473 
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;
476 
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;
480         }
481     }
482 
483     return 0;
484 }
485 
486 static int is_connection_trusted(connection * const con, plugin_data *p)
487 {
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));
490 }
491 
492 /*
493  * Return last address of proxy that is not trusted.
494  * Do not accept "all" keyword here.
495  */
496 static const char *last_not_in_array(array *a, plugin_data *p)
497 {
498 	int i;
499 
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;
504 		}
505 	}
506 	return NULL;
507 }
508 
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];
512 
513 	if (con->conf.log_request_handling) {
514 		log_error_write(srv, __FILE__, __LINE__, "ss", "using address:", addr);
515 	}
516 
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;
520 
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");
527 			}
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;
532 		}
533 	} else {
534 		con->plugin_ctx[p->id] = hctx = handler_ctx_init();
535 	}
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));
539 	}
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);
545 
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);
549 	}
550 
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);
553 
554 	return 1;
555 }
556 
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.
569 		 */
570 		if (extforward_check_proxy) {
571 			http_header_env_set(con, CONST_STR_LEN("_L_EXTFORWARD_ACTUAL_PROTO"), CONST_BUF_LEN(con->uri.scheme));
572 		}
573 		if (0 == buffer_caseless_compare(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 (0 == buffer_caseless_compare(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);
579 		}
580 	}
581 }
582 
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)
595 		 */
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));
599 		}
600 	}
601 	array_free(forward_array);
602 	return HANDLER_GO_ON;
603 }
604 
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;
610 }
611 
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;
617         }
618     }
619     return i;
620 }
621 
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;
627         }
628     }
629     return i;
630 }
631 
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);
637 
638     if (NULL == p) return 1; /*(nothing to do)*/
639 
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)*/
644         }
645         p[j++] = p[i];
646     }
647     buffer_string_set_length(b, (size_t)(p+j - b->ptr));
648     return 1;
649 }
650 
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.
666      */
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;
680         }
681         if (s[i] == '\0') break;
682 
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;
692         }
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;
704         }
705         vlen = i - v;              /* might be 0 */
706 
707         /* have k, klen, v, vlen
708          * (might contain quoted string) (contents not validated or decoded)
709          * (might be repeated k)
710          */
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;
718     }
719 
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;
727     }
728 
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 */
732 
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                   || 0 != buffer_caseless_compare(s+offsets[j], 3, "for", 3))
739                  && 0 != j-- && -1 != offsets[j]);
740         if (j < 0) break;
741         if (-1 == offsets[j]) { --j; continue; }
742 
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;
762                 }
763             }
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) ;
767             }
768             offsets[j+2] = v;
769         }
770         offsets[j+3] = vlen - v;
771 
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. */
778 
779         if (v != vlen) {
780             int trusted = is_proxy_trusted(p, s+v, vlen-v);
781 
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 */
785             }
786 
787             if (!trusted) break;
788         }
789 
790         do { --j; } while (j > 0 && -1 != offsets[j]);
791         if (j <= 0) break;
792         --j;
793     }
794 
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 */
804     }
805     else {
806         return HANDLER_GO_ON; /* make no changes */
807     }
808 
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 (0 == buffer_caseless_compare(s+offsets[j],2,"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 (0 == buffer_caseless_compare(s+offsets[j],3,"for",3))
827                 ofor = j;
828             break;
829          #endif
830           case 4:
831             if (0 == buffer_caseless_compare(s+offsets[j],4,"host",4))
832                 ohost = j;
833             break;
834           case 5:
835             if (0 == buffer_caseless_compare(s+offsets[j],5,"proto",5))
836                 oproto = j;
837             break;
838           case 11:
839             if (0 == buffer_caseless_compare(s+offsets[j],11,"remote_user",11))
840                 oremote_user = j;
841             break;
842           default:
843             break;
844         }
845     }
846     i = ++j;
847 
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);
856     }
857 
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
868          *
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
872          */
873 
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                 && 0 == buffer_caseless_compare(s+offsets[j], 4, "host", 4))
880                 ohost = j;
881             j += 4; /*(k, klen, v, vlen come in sets of 4)*/
882         }
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));
889             }
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;
903                 }
904             }
905             else {
906                 buffer_copy_string_len(con->request.http_host, s+v, vlen-v);
907             }
908 
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;
917             }
918 
919             config_cond_cache_reset_item(srv, con, COMP_HTTP_HOST);
920         }
921     }
922 
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                 && 0==buffer_caseless_compare(s+offsets[j],11,"remote_user",11))
930                 oremote_user = j;
931             j += 4; /*(k, klen, v, vlen come in sets of 4)*/
932         }
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;
952                 }
953             }
954             else {
955                 http_header_env_set(con,
956                                     CONST_STR_LEN("REMOTE_USER"), s+v, vlen-v);
957             }
958         }
959     }
960 
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                 && 0 == buffer_caseless_compare(s+offsets[j], 3, "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];
986                         }
987                         buffer_string_set_length(xff, x - xff->ptr);
988                     }
989                 }
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;
995             }
996             j += 4; /*(k, klen, v, vlen come in sets of 4)*/
997         }
998         http_header_request_set(con, HTTP_HEADER_X_FORWARDED_FOR, CONST_STR_LEN("X-Forwarded-For"), CONST_BUF_LEN(xff));
999     }
1000   #endif
1001 
1002     return HANDLER_GO_ON;
1003 }
1004 
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;
1010 
1011 	mod_extforward_patch_connection(srv, con, p);
1012 
1013 	if (con->conf.log_request_handling) {
1014 		log_error_write(srv, __FILE__, __LINE__, "s",
1015 			"-- mod_extforward_uri_handler called");
1016 	}
1017 
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"));
1035 		}
1036 	}
1037 
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;
1044 		}
1045 	}
1046 	if (NULL == forwarded) {
1047 		if (con->conf.log_request_handling) {
1048 			log_error_write(srv, __FILE__, __LINE__, "s", "no forward header found, skipping");
1049 		}
1050 
1051 		return HANDLER_GO_ON;
1052 	}
1053 
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");
1059 		}
1060 
1061 		return HANDLER_GO_ON;
1062 	}
1063 
1064 	if (is_forwarded_header) {
1065 		return mod_extforward_Forwarded(srv, con, p, forwarded);
1066 	}
1067 
1068 	return mod_extforward_X_Forwarded_For(srv, con, p, forwarded);
1069 }
1070 
1071 
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));
1083     }
1084     return HANDLER_GO_ON;
1085 }
1086 
1087 
1088 CONNECTION_FUNC(mod_extforward_restore) {
1089 	plugin_data *p = p_d;
1090 	handler_ctx *hctx = con->plugin_ctx[p->id];
1091 
1092 	if (!hctx) return HANDLER_GO_ON;
1093 
1094 	if (NULL != hctx->saved_network_read) {
1095 		con->network_read = hctx->saved_network_read;
1096 		hctx->saved_network_read = NULL;
1097 	}
1098 
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);
1106 	}
1107 
1108 	if (NULL == hctx->env) {
1109 		handler_ctx_free(hctx);
1110 		con->plugin_ctx[p->id] = NULL;
1111 	}
1112 
1113 	return HANDLER_GO_ON;
1114 }
1115 
1116 
1117 CONNECTION_FUNC(mod_extforward_handle_con_close)
1118 {
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;
1125         }
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;
1130         }
1131         if (NULL != hctx->env) {
1132             array_free(hctx->env);
1133         }
1134         handler_ctx_free(hctx);
1135         con->plugin_ctx[p->id] = NULL;
1136     }
1137 
1138     return HANDLER_GO_ON;
1139 }
1140 
1141 
1142 static int mod_extforward_network_read (server *srv, connection *con, chunkqueue *cq, off_t max_bytes);
1143 
1144 CONNECTION_FUNC(mod_extforward_handle_con_accept)
1145 {
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;
1154     }
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");
1160         }
1161     }
1162     return HANDLER_GO_ON;
1163 }
1164 
1165 
1166 /* this function is called at dlopen() time and inits the callbacks */
1167 
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");
1172 
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;
1182 
1183 	p->data        = NULL;
1184 
1185 	return 0;
1186 }
1187 
1188 
1189 
1190 
1191 /* Modified from:
1192  *   http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
1193  *
1194 9. Sample code
1195 
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.
1201  *
1202  */
1203 
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;
1232 };
1233 
1234 /*
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.
1239 
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.
1245  */
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 */
1251 };
1252 
1253 /*
1254 The following types have already been registered for the <type> field :
1255  */
1256 
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
1268 
1269 /*
1270 For the type PP2_TYPE_SSL, the value is itselv a defined like this :
1271  */
1272 
1273 struct pp2_tlv_ssl {
1274     uint8_t  client;
1275     uint32_t verify;
1276     /*struct pp2_tlv sub_tlv[0];*//* C99 zero-length array */
1277 };
1278 
1279 /*
1280 And the <client> field is made of a bit field from the following values,
1281 indicating which element is present :
1282  */
1283 
1284 #define PP2_CLIENT_SSL            0x01
1285 #define PP2_CLIENT_CERT_CONN      0x02
1286 #define PP2_CLIENT_CERT_SESS      0x04
1287 
1288 
1289 
1290 
1291 #ifndef MSG_DONTWAIT
1292 #define MSG_DONTWAIT 0
1293 #endif
1294 #ifndef MSG_NOSIGNAL
1295 #define MSG_NOSIGNAL 0
1296 #endif
1297 
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)
1300 {
1301     static const char v2sig[12] =
1302         "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A";
1303 
1304     ssize_t ret;
1305     size_t sz;
1306     int ver;
1307 
1308     do {
1309         ret = recv(fd, hdr, sizeof(*hdr), MSG_PEEK|MSG_DONTWAIT|MSG_NOSIGNAL);
1310     } while (-1 == ret && errno == EINTR);
1311 
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;
1320 
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 */
1327 
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 */
1332         }
1333     }
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 */
1340     }
1341     else {
1342         /* Wrong protocol */
1343         return -2;
1344     }
1345 
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;
1355         }
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;
1363     }
1364     if (1 == ver) hdr->v1.line[sz-2] = '\0'; /*terminate str to ease parsing*/
1365     return ver;
1366 }
1367 
1368 
1369 static int mod_extforward_hap_PROXY_v1 (connection * const con,
1370                                         union hap_PROXY_hdr * const hdr)
1371 {
1372   #ifdef __COVERITY__
1373     __coverity_tainted_data_sink__(hdr);
1374   #endif
1375 
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"
1381      */
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;
1393         }
1394         else {
1395             return -1;
1396         }
1397         s += 5;
1398     }
1399     else if (0 == memcmp(s, "UNKNOWN", sizeof("UNKNOWN")-1)
1400              && (s[7] == '\0' || s[7] == ' ')) {
1401         return 0;     /* keep local connection address */
1402     }
1403     else {
1404         return -1;
1405     }
1406 
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';
1418 
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;
1423 
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()) */
1429 
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);
1434 
1435     return 0;
1436 }
1437 
1438 
1439 static int mod_extforward_hap_PROXY_v2 (connection * const con,
1440                                         union hap_PROXY_hdr * const hdr)
1441 {
1442   #ifdef __COVERITY__
1443     __coverity_tainted_data_sink__(hdr);
1444   #endif
1445 
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.)
1457      */
1458 
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;*/
1465 
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.
1471      */
1472 
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) */
1475 
1476     struct pp2_tlv *tlv;
1477     uint32_t sz = ntohs(hdr->v2.len);
1478     uint32_t len = 0;
1479 
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()*/
1484     }
1485 
1486     /* PROXY command */
1487 
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 */
1519         {
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);
1526         }
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;
1537     }
1538 
1539     /* (optional) Type-Length-Value (TLV vectors) follow addresses */
1540 
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"));
1561             }
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;
1565             }
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;
1602                 }
1603             }
1604             break;
1605           }
1606          #if 0 /*(not implemented here)*/
1607           case PP2_TYPE_NETNS:
1608          #endif
1609           /*case PP2_TYPE_NOOP:*//* no-op */
1610           default:
1611             break;
1612         }
1613     }
1614 
1615     return 0;
1616 }
1617 
1618 
1619 static int mod_extforward_network_read (server *srv, connection *con,
1620                                         chunkqueue *cq, off_t max_bytes)
1621 {
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 */
1625 
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;
1641     }
1642 
1643     mod_extforward_restore(srv, con, mod_extforward_plugin_data_singleton);
1644     return (0 == rc) ? con->network_read(srv, con, cq, max_bytes) : rc;
1645 }
1646