xref: /lighttpd1.4/src/mod_extforward.c (revision df362494)
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 <limits.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <errno.h>
16 
17 #include "sys-socket.h"
18 
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 [email protected], #1528
23  *
24  *
25  * Mostly rewritten
26  * Portions:
27  * Copyright(c) 2017 Glenn Strauss gstrauss()gluelogic.com  All rights reserved
28  * License: BSD 3-clause (same as lighttpd)
29  *
30  * Config example:
31  *
32  *       Trust proxy 10.0.0.232 and 10.0.0.232
33  *       extforward.forwarder = ( "10.0.0.232" => "trust",
34  *                                "10.0.0.233" => "trust" )
35  *
36  *       Trust all proxies  (NOT RECOMMENDED!)
37  *       extforward.forwarder = ( "all" => "trust")
38  *
39  *       Note that "all" has precedence over specific entries,
40  *       so "all except" setups will not work.
41  *
42  *       In case you have chained proxies, you can add all their IP's to the
43  *       config. However "all" has effect only on connecting IP, as the
44  *       X-Forwarded-For header can not be trusted.
45  *
46  * Note: The effect of this module is variable on $HTTP["remoteip"] directives and
47  *       other module's remote ip dependent actions.
48  *  Things done by modules before we change the remoteip or after we reset it will match on the proxy's IP.
49  *  Things done in between these two moments will match on the real client's IP.
50  *  The moment things are done by a module depends on in which hook it does things and within the same hook
51  *  on whether they are before/after us in the module loading order
52  *  (order in the server.modules directive in the config file).
53  */
54 
55 
56 typedef enum {
57 	PROXY_FORWARDED_NONE         = 0x00,
58 	PROXY_FORWARDED_FOR          = 0x01,
59 	PROXY_FORWARDED_PROTO        = 0x02,
60 	PROXY_FORWARDED_HOST         = 0x04,
61 	PROXY_FORWARDED_BY           = 0x08,
62 	PROXY_FORWARDED_REMOTE_USER  = 0x10
63 } proxy_forwarded_t;
64 
65 struct sock_addr_mask {
66   sock_addr addr;
67   int bits;
68 };
69 
70 struct forwarder_cfg {
71   const array *forwarder;
72   int forward_all;
73   uint32_t addrs_used;
74  #if defined(__STDC_VERSION__) && __STDC_VERSION__-0 >= 199901L /* C99 */
75   struct sock_addr_mask addrs[];
76  #else
77   struct sock_addr_mask addrs[1];
78  #endif
79 };
80 
81 typedef struct {
82     const array *forwarder;
83     int forward_all;
84     uint32_t forward_masks_used;
85     const struct sock_addr_mask *forward_masks;
86     const array *headers;
87     unsigned int opts;
88     char hap_PROXY;
89     char hap_PROXY_ssl_client_verify;
90 } plugin_config;
91 
92 typedef struct {
93     PLUGIN_DATA;
94     plugin_config defaults;
95     plugin_config conf;
96     array *default_headers;
97     array tokens;
98 } plugin_data;
99 
100 static plugin_data *mod_extforward_plugin_data_singleton;
101 static int extforward_check_proxy;
102 
103 
104 /* context , used for restore remote ip */
105 
106 typedef struct {
107 	/* per-request state */
108 	sock_addr saved_remote_addr;
109 	buffer saved_remote_addr_buf;
110 
111 	/* hap-PROXY protocol prior to receiving first request */
112 	int(*saved_network_read)(connection *, chunkqueue *, off_t);
113 
114 	/* connection-level state applied to requests in handle_request_env */
115 	array *env;
116 	int ssl_client_verify;
117 	uint32_t request_count;
118 } handler_ctx;
119 
120 
121 static handler_ctx * handler_ctx_init(void) {
122     return ck_calloc(1, sizeof(handler_ctx));
123 }
124 
125 static void handler_ctx_free(handler_ctx *hctx) {
126 	free(hctx->saved_remote_addr_buf.ptr);
127 	free(hctx);
128 }
129 
130 INIT_FUNC(mod_extforward_init) {
131     return ck_calloc(1, sizeof(plugin_data));
132 }
133 
134 FREE_FUNC(mod_extforward_free) {
135     plugin_data * const p = p_d;
136     array_free(p->default_headers);
137     array_free_data(&p->tokens);
138     if (NULL == p->cvlist) return;
139     /* (init i to 0 if global context; to 1 to skip empty global context) */
140     for (int i = !p->cvlist[0].v.u2[1], used = p->nconfig; i < used; ++i) {
141         config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
142         for (; -1 != cpv->k_id; ++cpv) {
143             switch (cpv->k_id) {
144               case 0: /* extforward.forwarder */
145                 if (cpv->vtype == T_CONFIG_LOCAL) free(cpv->v.v);
146                 break;
147               default:
148                 break;
149             }
150         }
151     }
152 }
153 
154 static void mod_extforward_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) {
155     switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */
156       case 0: /* extforward.forwarder */
157         if (cpv->vtype == T_CONFIG_LOCAL) {
158             const struct forwarder_cfg * const fwd = cpv->v.v;
159             pconf->forwarder = fwd->forwarder;
160             pconf->forward_all = fwd->forward_all;
161             pconf->forward_masks_used = fwd->addrs_used;
162             pconf->forward_masks = fwd->addrs;
163         }
164         break;
165       case 1: /* extforward.headers */
166         pconf->headers = cpv->v.a;
167         break;
168       case 2: /* extforward.params */
169         if (cpv->vtype == T_CONFIG_LOCAL)
170             pconf->opts = cpv->v.u;
171         break;
172       case 3: /* extforward.hap-PROXY */
173         pconf->hap_PROXY = (char)cpv->v.u;
174         break;
175       case 4: /* extforward.hap-PROXY-ssl-client-verify */
176         pconf->hap_PROXY_ssl_client_verify = (char)cpv->v.u;
177         break;
178       default:/* should not happen */
179         return;
180     }
181 }
182 
183 static void mod_extforward_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) {
184     do {
185         mod_extforward_merge_config_cpv(pconf, cpv);
186     } while ((++cpv)->k_id != -1);
187 }
188 
189 static void mod_extforward_patch_config(request_st * const r, plugin_data * const p) {
190     memcpy(&p->conf, &p->defaults, sizeof(plugin_config));
191     for (int i = 1, used = p->nconfig; i < used; ++i) {
192         if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id))
193             mod_extforward_merge_config(&p->conf, p->cvlist + p->cvlist[i].v.u2[0]);
194     }
195 }
196 
197 static void * mod_extforward_parse_forwarder(server *srv, const array *forwarder) {
198     const data_string * const allds = (const data_string *)
199       array_get_element_klen(forwarder, CONST_STR_LEN("all"));
200     const int forward_all = (NULL == allds)
201       ? 0
202       : buffer_eq_icase_slen(&allds->value, CONST_STR_LEN("trust")) ? 1 : -1;
203     uint32_t nmasks = 0;
204     for (uint32_t j = 0; j < forwarder->used; ++j) {
205         data_string * const ds = (data_string *)forwarder->data[j];
206         char * const nm_slash = strchr(ds->key.ptr, '/');
207         if (NULL != nm_slash) ++nmasks;
208         if (!buffer_eq_icase_slen(&ds->value, CONST_STR_LEN("trust"))) {
209             if (!buffer_eq_icase_slen(&ds->value, CONST_STR_LEN("untrusted")))
210                 log_error(srv->errh, __FILE__, __LINE__,
211                   "ERROR: expect \"trust\", not \"%s\" => \"%s\"; "
212                   "treating as untrusted", ds->key.ptr, ds->value.ptr);
213             if (NULL != nm_slash) {
214                 /* future: consider adding member next to bits in sock_addr_mask
215                  *         with bool trusted/untrusted member */
216                 --nmasks;
217                 log_error(srv->errh, __FILE__, __LINE__,
218                   "ERROR: untrusted CIDR masks are ignored (\"%s\" => \"%s\")",
219                   ds->key.ptr, ds->value.ptr);
220             }
221             buffer_clear(&ds->value); /* empty is untrusted */
222             continue;
223         }
224     }
225 
226     struct forwarder_cfg * const fwd =
227       ck_calloc(1, sizeof(struct forwarder_cfg)
228                  + sizeof(struct sock_addr_mask)*nmasks);
229     fwd->forwarder = forwarder;
230     fwd->forward_all = forward_all;
231     fwd->addrs_used = 0;
232     for (uint32_t j = 0; j < forwarder->used; ++j) {
233         data_string * const ds = (data_string *)forwarder->data[j];
234         char * const nm_slash = strchr(ds->key.ptr, '/');
235         if (NULL == nm_slash) continue;
236         if (buffer_is_blank(&ds->value)) continue; /* ignored */
237 
238         char *err;
239         const int nm_bits = strtol(nm_slash + 1, &err, 10);
240         int rc;
241         if (*err || nm_bits <= 0 || !light_isdigit(nm_slash[1])) {
242             log_error(srv->errh, __FILE__, __LINE__,
243               "ERROR: invalid netmask: %s %s", ds->key.ptr, err);
244             free(fwd);
245             return NULL;
246         }
247         struct sock_addr_mask * const sm = fwd->addrs + fwd->addrs_used++;
248         sm->bits = nm_bits;
249         *nm_slash = '\0';
250         if (ds->key.ptr[0] == '['
251             && ds->key.ptr+1 < nm_slash && nm_slash[-1] == ']') {
252             nm_slash[-1] = '\0';
253             rc = sock_addr_from_str_numeric(&sm->addr,ds->key.ptr+1,srv->errh);
254             nm_slash[-1] = ']';
255         }
256         else
257             rc = sock_addr_from_str_numeric(&sm->addr,ds->key.ptr,  srv->errh);
258         *nm_slash = '/';
259         if (1 != rc) {
260             free(fwd);
261             return NULL;
262         }
263         buffer_clear(&ds->value);
264         /* empty is untrusted,
265          * e.g. if subnet (incorrectly) appears in X-Forwarded-For */
266     }
267 
268     return fwd;
269 }
270 
271 static unsigned int mod_extforward_parse_opts(server *srv, const array *opts_params) {
272     unsigned int opts = 0;
273     for (uint32_t j = 0, used = opts_params->used; j < used; ++j) {
274         proxy_forwarded_t param;
275         data_unset *du = opts_params->data[j];
276       #if 0  /*("for" and "proto" historical behavior: always enabled)*/
277         if (buffer_eq_slen(&du->key, CONST_STR_LEN("by")))
278             param = PROXY_FORWARDED_BY;
279         else if (buffer_eq_slen(&du->key, CONST_STR_LEN("for")))
280             param = PROXY_FORWARDED_FOR;
281         else
282       #endif
283         if (buffer_eq_slen(&du->key, CONST_STR_LEN("host")))
284             param = PROXY_FORWARDED_HOST;
285       #if 0
286         else if (buffer_eq_slen(&du->key, CONST_STR_LEN("proto")))
287             param = PROXY_FORWARDED_PROTO;
288       #endif
289         else if (buffer_eq_slen(&du->key, CONST_STR_LEN("remote_user")))
290             param = PROXY_FORWARDED_REMOTE_USER;
291         else {
292             log_error(srv->errh, __FILE__, __LINE__,
293               "extforward.params keys must be one of: "
294               "host, remote_user, but not: %s", du->key.ptr);
295             return UINT_MAX;
296         }
297 
298         int val = config_plugin_value_tobool(du, 2);
299         if (2 == val) {
300             log_error(srv->errh, __FILE__, __LINE__,
301               "extforward.params values must be one of: "
302               "0, 1, enable, disable; error for key: %s", du->key.ptr);
303             return UINT_MAX;
304         }
305         if (val)
306             opts |= param;
307     }
308     return opts;
309 }
310 
311 SETDEFAULTS_FUNC(mod_extforward_set_defaults) {
312     static const config_plugin_keys_t cpk[] = {
313       { CONST_STR_LEN("extforward.forwarder"),
314         T_CONFIG_ARRAY_KVSTRING,
315         T_CONFIG_SCOPE_CONNECTION }
316      ,{ CONST_STR_LEN("extforward.headers"),
317         T_CONFIG_ARRAY_VLIST,
318         T_CONFIG_SCOPE_CONNECTION }
319      ,{ CONST_STR_LEN("extforward.params"),
320         T_CONFIG_ARRAY_KVANY,
321         T_CONFIG_SCOPE_CONNECTION }
322      ,{ CONST_STR_LEN("extforward.hap-PROXY"),
323         T_CONFIG_BOOL,
324         T_CONFIG_SCOPE_CONNECTION }
325      ,{ CONST_STR_LEN("extforward.hap-PROXY-ssl-client-verify"),
326         T_CONFIG_BOOL,
327         T_CONFIG_SCOPE_CONNECTION }
328      ,{ NULL, 0,
329         T_CONFIG_UNSET,
330         T_CONFIG_SCOPE_UNSET }
331     };
332 
333     plugin_data * const p = p_d;
334     if (!config_plugin_values_init(srv, p, cpk, "mod_extforward"))
335         return HANDLER_ERROR;
336 
337     int hap_PROXY = 0;
338 
339     /* process and validate config directives
340      * (init i to 0 if global context; to 1 to skip empty global context) */
341     for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) {
342         config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
343         for (; -1 != cpv->k_id; ++cpv) {
344             switch (cpv->k_id) {
345               case 0: /* extforward.forwarder */
346                 cpv->v.v = mod_extforward_parse_forwarder(srv, cpv->v.a);
347                 if (NULL == cpv->v.v) {
348                     log_error(srv->errh, __FILE__, __LINE__,
349                       "unexpected value for %s", cpk[cpv->k_id].k);
350                     return HANDLER_ERROR;
351                 }
352                 cpv->vtype = T_CONFIG_LOCAL;
353                 break;
354               case 1: /* extforward.headers */
355                 if (cpv->v.a->used) {
356                     array *a;
357                     *(const array **)&a = cpv->v.a;
358                     for (uint32_t j = 0; j < a->used; ++j) {
359                         data_string * const ds = (data_string *)a->data[j];
360                         ds->ext =
361                           http_header_hkey_get(BUF_PTR_LEN(&ds->value));
362                     }
363                 }
364                 break;
365               case 2: /* extforward.params */
366                 cpv->v.u = mod_extforward_parse_opts(srv, cpv->v.a);
367                 if (UINT_MAX == cpv->v.u)
368                     return HANDLER_ERROR;
369                 break;
370               case 3: /* extforward.hap-PROXY */
371                 if (cpv->v.u) hap_PROXY = 1;
372                 break;
373               case 4: /* extforward.hap-PROXY-ssl-client-verify */
374                 break;
375               default:/* should not happen */
376                 break;
377             }
378         }
379     }
380 
381     mod_extforward_plugin_data_singleton = p;
382     p->defaults.opts = PROXY_FORWARDED_NONE;
383 
384     /* initialize p->defaults from global config context */
385     if (p->nconfig > 0 && p->cvlist->v.u2[1]) {
386         const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0];
387         if (-1 != cpv->k_id)
388             mod_extforward_merge_config(&p->defaults, cpv);
389     }
390 
391     /* default to "X-Forwarded-For" or "Forwarded-For" if extforward.headers
392      * is not specified or is empty (and not using hap_PROXY) */
393     if (!p->defaults.hap_PROXY
394         && (NULL == p->defaults.headers || 0 == p->defaults.headers->used)) {
395         p->defaults.headers = p->default_headers = array_init(2);
396         array_insert_value(p->default_headers,CONST_STR_LEN("X-Forwarded-For"));
397         array_insert_value(p->default_headers,CONST_STR_LEN("Forwarded-For"));
398         for (uint32_t i = 0; i < p->default_headers->used; ++i) {
399             data_string * const ds = (data_string *)p->default_headers->data[i];
400             ds->ext = http_header_hkey_get(BUF_PTR_LEN(&ds->value));
401         }
402     }
403 
404     /* attempt to warn if mod_extforward is not last module loaded to hook
405      * handle_connection_accept.  (Nice to have, but remove this check if
406      * it reaches too far into internals and prevents other code changes.)
407      * While it would be nice to check handle_connection_accept plugin slot
408      * to make sure mod_extforward is last, that info is private to plugin.c
409      * so merely warn if mod_openssl is loaded after mod_extforward, though
410      * future modules which hook handle_connection_accept might be missed.*/
411     if (hap_PROXY) {
412         uint32_t i;
413         for (i = 0; i < srv->srvconf.modules->used; ++i) {
414             data_string *ds = (data_string *)srv->srvconf.modules->data[i];
415             if (buffer_eq_slen(&ds->value, CONST_STR_LEN("mod_extforward")))
416                 break;
417         }
418         for (; i < srv->srvconf.modules->used; ++i) {
419             data_string *ds = (data_string *)srv->srvconf.modules->data[i];
420             if (buffer_eq_slen(&ds->value, CONST_STR_LEN("mod_openssl"))
421                 || buffer_eq_slen(&ds->value, CONST_STR_LEN("mod_mbedtls"))
422                 || buffer_eq_slen(&ds->value, CONST_STR_LEN("mod_wolfssl"))
423                 || buffer_eq_slen(&ds->value, CONST_STR_LEN("mod_nss"))
424                 || buffer_eq_slen(&ds->value, CONST_STR_LEN("mod_gnutls"))) {
425                 log_error(srv->errh, __FILE__, __LINE__,
426                   "mod_extforward must be loaded after %s in "
427                   "server.modules when extforward.hap-PROXY = \"enable\"",
428                   ds->value.ptr);
429                 break;
430             }
431         }
432     }
433 
434     for (uint32_t i = 0; i < srv->srvconf.modules->used; ++i) {
435         data_string *ds = (data_string *)srv->srvconf.modules->data[i];
436         if (buffer_is_equal_string(&ds->value, CONST_STR_LEN("mod_proxy"))) {
437             extforward_check_proxy = 1;
438             break;
439         }
440     }
441 
442     return HANDLER_GO_ON;
443 }
444 
445 
446 /*
447    extract a forward array from the environment
448 */
449 static void extract_forward_array(array * const result, const buffer *pbuffer)
450 {
451 		/*force_assert(!buffer_is_blank(pbuffer));*/
452 		const char *base, *curr;
453 		/* state variable, 0 means not in string, 1 means in string */
454 		int in_str = 0;
455 		for (base = pbuffer->ptr, curr = pbuffer->ptr; *curr; curr++) {
456 			int hex_or_colon = (light_isxdigit(*curr) || *curr == ':');
457 			if (in_str) {
458 				if (!hex_or_colon && *curr != '.') {
459 					/* found an separator , insert value into result array */
460 					array_insert_value(result, base, curr - base);
461 					/* change state to not in string */
462 					in_str = 0;
463 				}
464 			} else {
465 				if (hex_or_colon) {
466 					/* found leading char of an IP address, move base pointer and change state */
467 					base = curr;
468 					in_str = 1;
469 				}
470 			}
471 		}
472 		/* if breaking out while in str, we got to the end of string, so add it */
473 		if (in_str) {
474 			array_insert_value(result, base, curr - base);
475 		}
476 }
477 
478 /*
479  * check whether ip is trusted, return 1 for trusted , 0 for untrusted
480  */
481 static int is_proxy_trusted(plugin_data *p, const char * const ip, size_t iplen)
482 {
483     const data_string *ds =
484       (const data_string *)array_get_element_klen(p->conf.forwarder, ip, iplen);
485     if (NULL != ds) return !buffer_is_blank(&ds->value);
486 
487     if (p->conf.forward_masks_used) {
488         const struct sock_addr_mask * const addrs = p->conf.forward_masks;
489         const uint32_t aused = p->conf.forward_masks_used;
490         sock_addr addr;
491         /* C funcs inet_aton(), inet_pton() require '\0'-terminated IP str */
492         char addrstr[64]; /*(larger than INET_ADDRSTRLEN and INET6_ADDRSTRLEN)*/
493         if (0 == iplen || iplen >= sizeof(addrstr)) return 0;
494         memcpy(addrstr, ip, iplen);
495         addrstr[iplen] = '\0';
496 
497         if (1 != sock_addr_inet_pton(&addr, addrstr, AF_INET,  0)
498          && 1 != sock_addr_inet_pton(&addr, addrstr, AF_INET6, 0)) return 0;
499 
500         for (uint32_t i = 0; i < aused; ++i) {
501             if (sock_addr_is_addr_eq_bits(&addr, &addrs[i].addr, addrs[i].bits))
502                 return 1;
503         }
504     }
505 
506     return 0;
507 }
508 
509 static int is_connection_trusted(connection * const con, plugin_data *p)
510 {
511     if (p->conf.forward_all) return (1 == p->conf.forward_all);
512     return is_proxy_trusted(p, BUF_PTR_LEN(&con->dst_addr_buf));
513 }
514 
515 /*
516  * Return last address of proxy that is not trusted.
517  * Do not accept "all" keyword here.
518  */
519 static const buffer *last_not_in_array(array *a, plugin_data *p)
520 {
521 	int i;
522 
523 	for (i = a->used - 1; i >= 0; i--) {
524 		data_string *ds = (data_string *)a->data[i];
525 		if (!is_proxy_trusted(p, BUF_PTR_LEN(&ds->value))) {
526 			return &ds->value;
527 		}
528 	}
529 	return NULL;
530 }
531 
532 static int mod_extforward_set_addr(request_st * const r, plugin_data *p, const char *addr, size_t addrlen) {
533 	connection * const con = r->con;
534 	sock_addr sock;
535 	handler_ctx *hctx = con->plugin_ctx[p->id];
536 
537 	/* Preserve changed addr for lifetime of h2 connection; upstream proxy
538 	 * should not reuse same h2 connection for requests from different clients*/
539 	if (hctx && !buffer_is_unset(&hctx->saved_remote_addr_buf)
540 	    && r->http_version > HTTP_VERSION_1_1) { /*(e.g. HTTP_VERSION_2)*/
541 		hctx->request_count = con->request_count;
542 		if (extforward_check_proxy) /* save old address */
543 			http_header_env_set(r, CONST_STR_LEN("_L_EXTFORWARD_ACTUAL_FOR"),
544 			                    BUF_PTR_LEN(&hctx->saved_remote_addr_buf));
545 		return 1;
546 	}
547 
548 	if (r->conf.log_request_handling) {
549 		log_error(r->conf.errh, __FILE__, __LINE__, "using address: %s", addr);
550 	}
551 
552 	sock.plain.sa_family = AF_UNSPEC;
553 	if (1 != sock_addr_from_str_numeric(&sock, addr, r->conf.errh)) return 0;
554 	if (sock.plain.sa_family == AF_UNSPEC) return 0;
555 
556 	/* we found the remote address, modify current connection and save the old address */
557 	if (hctx) {
558 		if (!buffer_is_unset(&hctx->saved_remote_addr_buf)) {
559 			if (r->conf.log_request_handling) {
560 				log_error(r->conf.errh, __FILE__, __LINE__,
561 				  "-- mod_extforward_uri_handler already patched this connection, resetting state");
562 			}
563 			con->dst_addr = hctx->saved_remote_addr;
564 			buffer_move(&con->dst_addr_buf, &hctx->saved_remote_addr_buf);
565 		}
566 	} else {
567 		con->plugin_ctx[p->id] = hctx = handler_ctx_init();
568 	}
569 	/* save old address */
570 	if (extforward_check_proxy) {
571 		http_header_env_set(r, CONST_STR_LEN("_L_EXTFORWARD_ACTUAL_FOR"),
572 		                    BUF_PTR_LEN(&con->dst_addr_buf));
573 	}
574 	hctx->request_count = con->request_count;
575 	hctx->saved_remote_addr = con->dst_addr;
576 	buffer_move(&hctx->saved_remote_addr_buf, &con->dst_addr_buf);
577 	/* patch connection address */
578 	con->dst_addr = sock;
579 	buffer_copy_string_len(&con->dst_addr_buf, addr, addrlen);
580 
581 	/* Now, clean the conf_cond cache, because we may have changed the results of tests */
582 	config_cond_cache_reset_item(r, COMP_HTTP_REMOTE_IP);
583 
584 	return 1;
585 }
586 
587 static void mod_extforward_set_proto(request_st * const r, const char * const proto, size_t protolen) {
588 	if (0 != protolen && !buffer_eq_icase_slen(&r->uri.scheme, proto, protolen)) {
589 		/* update scheme if X-Forwarded-Proto is set
590 		 * Limitations:
591 		 * - Only "http" or "https" are currently accepted since the request to lighttpd currently has to
592 		 *   be HTTP/1.0 or HTTP/1.1 using http or https.  If this is changed, then the scheme from this
593 		 *   untrusted header must be checked to contain only alphanumeric characters, and to be a
594 		 *   reasonable length, e.g. < 256 chars.
595 		 * - r->uri.scheme is not reset in mod_extforward_restore() but is currently not an issues since
596 		 *   r->uri.scheme will be reset by next request.  If a new module uses r->uri.scheme in the
597 		 *   handle_request_done hook, then should evaluate if that module should use the forwarded value
598 		 *   (probably) or the original value.
599 		 */
600 		if (extforward_check_proxy) {
601 			http_header_env_set(r, CONST_STR_LEN("_L_EXTFORWARD_ACTUAL_PROTO"), BUF_PTR_LEN(&r->uri.scheme));
602 		}
603 		if (buffer_eq_icase_ss(proto, protolen, CONST_STR_LEN("https"))) {
604 	                r->con->proto_default_port = 443; /* "https" */
605 			buffer_copy_string_len(&r->uri.scheme, CONST_STR_LEN("https"));
606 			config_cond_cache_reset_item(r, COMP_HTTP_SCHEME);
607 		} else if (buffer_eq_icase_ss(proto, protolen, CONST_STR_LEN("http"))) {
608 	                r->con->proto_default_port = 80; /* "http" */
609 			buffer_copy_string_len(&r->uri.scheme, CONST_STR_LEN("http"));
610 			config_cond_cache_reset_item(r, COMP_HTTP_SCHEME);
611 		}
612 	}
613 }
614 
615 static handler_t mod_extforward_X_Forwarded_For(request_st * const r, plugin_data * const p, const buffer * const x_forwarded_for) {
616 	/* build forward_array from forwarded data_string */
617 	array * const forward_array = &p->tokens;
618 	extract_forward_array(forward_array, x_forwarded_for);
619 	const buffer *real_remote_addr = last_not_in_array(forward_array, p);
620 	if (real_remote_addr != NULL) { /* parsed */
621 		/* get scheme if X-Forwarded-Proto is set
622 		 * Limitations:
623 		 * - X-Forwarded-Proto may or may not be set by proxies, even if X-Forwarded-For is set
624 		 * - X-Forwarded-Proto may be a comma-separated list if there are multiple proxies,
625 		 *   but the historical behavior of the code below only honored it if there was exactly one value
626 		 *   (not done: walking backwards in X-Forwarded-Proto the same num of steps
627 		 *    as in X-Forwarded-For to find proto set by last trusted proxy)
628 		 */
629 		const buffer *x_forwarded_proto = http_header_request_get(r, HTTP_HEADER_X_FORWARDED_PROTO, CONST_STR_LEN("X-Forwarded-Proto"));
630 		if (mod_extforward_set_addr(r, p, BUF_PTR_LEN(real_remote_addr)) && NULL != x_forwarded_proto) {
631 			mod_extforward_set_proto(r, BUF_PTR_LEN(x_forwarded_proto));
632 		}
633 	}
634 	array_reset_data_strings(forward_array);
635 	return HANDLER_GO_ON;
636 }
637 
638 __attribute_pure__
639 static int find_end_quoted_string (const char * const s, int i) {
640     do {
641         ++i;
642     } while (s[i] != '"' && s[i] != '\0' && (s[i] != '\\' || s[++i] != '\0'));
643     return i;
644 }
645 
646 __attribute_pure__
647 static int find_next_semicolon_or_comma_or_eq (const char * const s, int i) {
648     for (; s[i] != '=' && s[i] != ';' && s[i] != ',' && s[i] != '\0'; ++i) {
649         if (s[i] == '"') {
650             i = find_end_quoted_string(s, i);
651             if (s[i] == '\0') return -1;
652         }
653     }
654     return i;
655 }
656 
657 __attribute_pure__
658 static int find_next_semicolon_or_comma (const char * const s, int i) {
659     for (; s[i] != ';' && s[i] != ',' && s[i] != '\0'; ++i) {
660         if (s[i] == '"') {
661             i = find_end_quoted_string(s, i);
662             if (s[i] == '\0') return -1;
663         }
664     }
665     return i;
666 }
667 
668 static int buffer_backslash_unescape (buffer * const b) {
669     /* (future: might move to buffer.c) */
670     size_t j = 0;
671     size_t len = buffer_clen(b);
672     char *p = memchr(b->ptr, '\\', len);
673 
674     if (NULL == p) return 1; /*(nothing to do)*/
675 
676     len -= (size_t)(p - b->ptr);
677     for (size_t i = 0; i < len; ++i) {
678         if (p[i] == '\\') {
679             if (++i == len) return 0; /*(invalid trailing backslash)*/
680         }
681         p[j++] = p[i];
682     }
683     buffer_truncate(b, (size_t)(p+j - b->ptr));
684     return 1;
685 }
686 
687 __attribute_cold__
688 static handler_t mod_extforward_bad_request (request_st * const r, const unsigned int line, const char * const msg)
689 {
690     r->http_status = 400; /* Bad Request */
691     r->handler_module = NULL;
692     log_error(r->conf.errh, __FILE__, line, "%s", msg);
693     return HANDLER_FINISHED;
694 }
695 
696 static handler_t mod_extforward_Forwarded (request_st * const r, plugin_data * const p, const buffer * const forwarded) {
697     /* HTTP list need not consist of param=value tokens,
698      * but this routine expect such for HTTP Forwarded header
699      * Since info in each set of params is only used if from
700      * admin-specified trusted proxy:
701      * - invalid param=value tokens are ignored and skipped
702      * - not checking "for" exists in each set of params
703      * - not checking for duplicated params in each set of params
704      * - not checking canonical form of addr (also might be obfuscated)
705      * - obfuscated tokens permitted in chain, though end of trust is expected
706      *   to be non-obfuscated IP for mod_extforward to masquerade as remote IP
707      * future: since (potentially) trusted proxies begin at end of string,
708      *   it might be better to parse from end of string rather than parsing from
709      *   beginning.  Doing so would also allow reducing arbitrary param limit
710      *   to number of params permitted per proxy.
711      */
712     char * const s = forwarded->ptr;
713     int i = 0, j = -1, v, vlen, k, klen;
714     int used = (int)buffer_clen(forwarded);
715     int ofor = -1, oproto, ohost, oby, oremote_user;
716     int offsets[256];/*(~50 params is more than reasonably expected to handle)*/
717     while (i < used) {
718         while (s[i] == ' ' || s[i] == '\t') ++i;
719         if (s[i] == ';') { ++i; continue; }
720         if (s[i] == ',') {
721             if (j >= (int)(sizeof(offsets)/sizeof(int))-1) break;
722             offsets[++j] = -1; /*("offset" separating params from next proxy)*/
723             ++i;
724             continue;
725         }
726         if (s[i] == '\0') break;
727 
728         k = i;
729         i = find_next_semicolon_or_comma_or_eq(s, i);
730         if (i < 0) {
731             /*(reject IP spoofing if attacker sets improper quoted-string)*/
732             return mod_extforward_bad_request(r, __LINE__,
733               "invalid quoted-string in Forwarded header");
734         }
735         if (s[i] != '=') continue;
736         klen = i - k;
737         v = ++i;
738         i = find_next_semicolon_or_comma(s, i);
739         if (i < 0) {
740             /*(reject IP spoofing if attacker sets improper quoted-string)*/
741             return mod_extforward_bad_request(r, __LINE__,
742               "invalid quoted-string in Forwarded header");
743         }
744         vlen = i - v;              /* might be 0 */
745 
746         /* have k, klen, v, vlen
747          * (might contain quoted string) (contents not validated or decoded)
748          * (might be repeated k)
749          */
750         if (0 == klen) continue;   /* invalid k */
751         if (j >= (int)(sizeof(offsets)/sizeof(int))-4) break;
752         offsets[j+1] = k;
753         offsets[j+2] = klen;
754         offsets[j+3] = v;
755         offsets[j+4] = vlen;
756         j += 4;
757     }
758 
759     if (j >= (int)(sizeof(offsets)/sizeof(int))-4) {
760         /* error processing Forwarded; too many params; fail closed */
761         return mod_extforward_bad_request(r, __LINE__,
762           "Too many params in Forwarded header");
763     }
764 
765     if (-1 == j) return HANDLER_GO_ON;  /* make no changes */
766     used = j+1;
767     offsets[used] = -1; /* mark end of last set of params */
768 
769     while (j >= 4) { /*(param=value pairs)*/
770         if (-1 == offsets[j]) { --j; continue; }
771         do {
772             j -= 3; /*(k, klen, v, vlen come in sets of 4)*/
773         } while ((3 != offsets[j+1]  /* 3 == sizeof("for")-1 */
774                   || !buffer_eq_icase_ssn(s+offsets[j], "for", 3))
775                  && 0 != j-- && -1 != offsets[j]);
776         if (j < 0) break;
777         if (-1 == offsets[j]) { --j; continue; }
778 
779         /* remove trailing spaces/tabs and double-quotes from string
780          * (note: not unescaping backslash escapes in quoted string) */
781         v = offsets[j+2];
782         vlen = v + offsets[j+3];
783         while (vlen > v && (s[vlen-1] == ' ' || s[vlen-1] == '\t')) --vlen;
784         if (vlen > v+1 && s[v] == '"' && s[vlen-1] == '"') {
785             offsets[j+2] = ++v;
786             --vlen;
787             if (s[v] == '[') {
788                 /* remove "[]" surrounding IPv6, as well as (optional) port
789                  * (assumes properly formatted IPv6 addr from trusted proxy) */
790                 ++v;
791                 do { --vlen; } while (vlen > v && s[vlen] != ']');
792                 if (v == vlen) {
793                     return mod_extforward_bad_request(r, __LINE__,
794                       "Invalid IPv6 addr in Forwarded header");
795                 }
796             }
797             else if (s[v] != '_' && s[v] != '/' && s[v] != 'u') {
798                 /* remove (optional) port from non-obfuscated IPv4 */
799                 for (klen=vlen, vlen=v; vlen < klen && s[vlen] != ':'; ++vlen) ;
800             }
801             offsets[j+2] = v;
802         }
803         offsets[j+3] = vlen - v;
804 
805         /* obfuscated ipstr and obfuscated port are also accepted here, as
806          * is path to unix domain socket, but note that backslash escapes
807          * in quoted-string were not unescaped above.  Also, if obfuscated
808          * identifiers are rotated by proxies as recommended by RFC, then
809          * maintaining list of trusted identifiers is non-trivial and is not
810          * attempted by this module. */
811 
812         if (v != vlen) {
813             int trusted = is_proxy_trusted(p, s+v, vlen-v);
814 
815             if (s[v] != '_' && s[v] != '/'
816                 && (7 != (vlen - v) || 0 != memcmp(s+v, "unknown", 7))) {
817                 ofor = j; /* save most recent non-obfuscated ipstr */
818             }
819 
820             if (!trusted) break;
821         }
822 
823         do { --j; } while (j > 0 && -1 != offsets[j]);
824         if (j <= 0) break;
825         --j;
826     }
827 
828     if (-1 != ofor) {
829         /* C funcs getaddrinfo(), inet_addr() require '\0'-terminated IP str */
830         char *ipend = s+offsets[ofor+2]+offsets[ofor+3];
831         char c = *ipend;
832         int rc;
833         *ipend = '\0';
834         rc = mod_extforward_set_addr(r, p, s+offsets[ofor+2], offsets[ofor+3]);
835         *ipend = c;
836         if (!rc) return HANDLER_GO_ON; /* invalid addr; make no changes */
837     }
838     else {
839         return HANDLER_GO_ON; /* make no changes */
840     }
841 
842     /* parse out params associated with for=<ip> addr set above */
843     oproto = ohost = oby = oremote_user = -1;
844     UNUSED(oby);
845     j = ofor;
846     if (j > 0) { do { --j; } while (j > 0 && -1 != offsets[j]); }
847     if (-1 == offsets[j]) ++j;
848     if (j == ofor) j += 4;
849     for (; -1 != offsets[j]; j+=4) { /*(k, klen, v, vlen come in sets of 4)*/
850         switch (offsets[j+1]) {
851          #if 0
852           case 2:
853             if (buffer_eq_icase_ssn(s+offsets[j], "by", 2))
854                 oby = j;
855             break;
856          #endif
857          #if 0
858           /*(already handled above to find IP prior to earliest trusted proxy)*/
859           case 3:
860             if (buffer_eq_icase_ssn(s+offsets[j], "for", 3))
861                 ofor = j;
862             break;
863          #endif
864           case 4:
865             if (buffer_eq_icase_ssn(s+offsets[j], "host", 4))
866                 ohost = j;
867             break;
868           case 5:
869             if (buffer_eq_icase_ssn(s+offsets[j], "proto", 5))
870                 oproto = j;
871             break;
872           case 11:
873             if (buffer_eq_icase_ssn(s+offsets[j], "remote_user", 11))
874                 oremote_user = j;
875             break;
876           default:
877             break;
878         }
879     }
880     i = j+1;
881 
882     if (-1 != oproto) {
883         /* remove trailing spaces/tabs, and double-quotes from proto
884          * (note: not unescaping backslash escapes in quoted string) */
885         v = offsets[oproto+2];
886         vlen = v + offsets[oproto+3];
887         while (vlen > v && (s[vlen-1] == ' ' || s[vlen-1] == '\t')) --vlen;
888         if (vlen > v+1 && s[v] == '"' && s[vlen-1] == '"') { ++v; --vlen; }
889         mod_extforward_set_proto(r, s+v, vlen-v);
890     }
891 
892     if (p->conf.opts & PROXY_FORWARDED_HOST) {
893         /* Limitations:
894          * - r->http_host is not reset in mod_extforward_restore()
895          *   but is currently not an issues since r->http_host will be
896          *   reset by next request.  If a new module uses r->http_host
897          *   in the handle_request_done hook, then should evaluate if that
898          *   module should use the forwarded value (probably) or original value.
899          * - due to need to decode and unescape host=..., some extra work is
900          *   done in the case where host matches current Host header.
901          *   future: might add code to check if Host has actually changed or not
902          *
903          * note: change host after mod_extforward_set_proto() since that may
904          *       affect scheme port used in http_request_host_policy() host
905          *       normalization
906          */
907 
908         /* find host param set by earliest trusted proxy in proxy chain
909          * (host might be changed anywhere along the chain) */
910         for (j = i; j < used && -1 == ohost; ) {
911             if (-1 == offsets[j]) { ++j; continue; }
912             if (4 == offsets[j+1]
913                 && buffer_eq_icase_ssn(s+offsets[j], "host", 4))
914                 ohost = j;
915             j += 4; /*(k, klen, v, vlen come in sets of 4)*/
916         }
917         if (-1 != ohost) {
918             if (r->http_host && !buffer_is_blank(r->http_host)) {
919                 if (extforward_check_proxy)
920                     http_header_env_set(r,
921                       CONST_STR_LEN("_L_EXTFORWARD_ACTUAL_HOST"),
922                       BUF_PTR_LEN(r->http_host));
923             }
924             else {
925                 r->http_host =
926                   http_header_request_set_ptr(r, HTTP_HEADER_HOST,
927                                               CONST_STR_LEN("Host"));
928             }
929             /* remove trailing spaces/tabs, and double-quotes from host */
930             v = offsets[ohost+2];
931             vlen = v + offsets[ohost+3];
932             while (vlen > v && (s[vlen-1] == ' ' || s[vlen-1] == '\t')) --vlen;
933             if (vlen > v+1 && s[v] == '"' && s[vlen-1] == '"') {
934                 ++v; --vlen;
935                 buffer_copy_string_len_lc(r->http_host, s+v, vlen-v);
936                 if (!buffer_backslash_unescape(r->http_host)) {
937                     return mod_extforward_bad_request(r, __LINE__,
938                       "invalid host= value in Forwarded header");
939                 }
940             }
941             else {
942                 buffer_copy_string_len_lc(r->http_host, s+v, vlen-v);
943             }
944 
945             if (0 != http_request_host_policy(r->http_host,
946                                               r->conf.http_parseopts,
947                                               r->con->proto_default_port)) {
948                 /*(reject invalid chars in Host)*/
949                 return mod_extforward_bad_request(r, __LINE__,
950                   "invalid host= value in Forwarded header");
951             }
952 
953             config_cond_cache_reset_item(r, COMP_HTTP_HOST);
954         }
955     }
956 
957     if (p->conf.opts & PROXY_FORWARDED_REMOTE_USER) {
958         /* find remote_user param set by closest proxy
959          * (auth may have been handled by any trusted proxy in proxy chain) */
960         for (j = i; j < used; ) {
961             if (-1 == offsets[j]) { ++j; continue; }
962             if (11 == offsets[j+1]
963                 && buffer_eq_icase_ssn(s+offsets[j], "remote_user", 11))
964                 oremote_user = j;
965             j += 4; /*(k, klen, v, vlen come in sets of 4)*/
966         }
967         if (-1 != oremote_user) {
968             /* ???: should we also support param for auth_type ??? */
969             /* remove trailing spaces/tabs, and double-quotes from remote_user*/
970             v = offsets[oremote_user+2];
971             vlen = v + offsets[oremote_user+3];
972             while (vlen > v && (s[vlen-1] == ' ' || s[vlen-1] == '\t')) --vlen;
973             if (vlen > v+1 && s[v] == '"' && s[vlen-1] == '"') {
974                 buffer *euser;
975                 ++v; --vlen;
976                 http_header_env_set(r,
977                                     CONST_STR_LEN("REMOTE_USER"), s+v, vlen-v);
978                 euser = http_header_env_get(r, CONST_STR_LEN("REMOTE_USER"));
979                 force_assert(NULL != euser);
980                 if (!buffer_backslash_unescape(euser)) {
981                     return mod_extforward_bad_request(r, __LINE__,
982                       "invalid remote_user= value in Forwarded header");
983                 }
984             }
985             else {
986                 http_header_env_set(r,
987                                     CONST_STR_LEN("REMOTE_USER"), s+v, vlen-v);
988             }
989         }
990     }
991 
992   #if 0
993     if ((p->conf.opts & PROXY_FORWARDED_CREATE_XFF)
994         && !light_btst(r->rqst_htags, HTTP_HEADER_X_FORWARDED_FOR)) {
995         /* create X-Forwarded-For if not present
996          * (and at least original connecting IP is a trusted proxy) */
997         buffer * const xff =
998           http_header_request_set_ptr(r, HTTP_HEADER_X_FORWARDED_FOR,
999                                       CONST_STR_LEN("X-Forwarded-For"));
1000         for (j = 0; j < used; ) {
1001             if (-1 == offsets[j]) { ++j; continue; }
1002             if (3 == offsets[j+1]
1003                 && buffer_eq_icase_ssn(s+offsets[j], "for", 3)) {
1004                 if (!buffer_is_blank(xff))
1005                     buffer_append_string_len(xff, CONST_STR_LEN(", "));
1006                 /* quoted-string, IPv6 brackets, and :port already removed */
1007                 v = offsets[j+2];
1008                 vlen = offsets[j+3];
1009                 buffer_append_string_len(xff, s+v, vlen);
1010                 if (s[v-1] != '=') { /*(must have been quoted-string)*/
1011                     char *x =
1012                       memchr(xff->ptr + buffer_clen(xff) - vlen, '\\', vlen);
1013                     if (NULL != x) { /* backslash unescape in-place */
1014                         for (v = 0; x[v]; ++x) {
1015                             if (x[v] == '\\' && x[++v] == '\0')
1016                                 break; /*(invalid trailing backslash)*/
1017                             *x = x[v];
1018                         }
1019                         buffer_truncate(xff, x - xff->ptr);
1020                     }
1021                 }
1022                 /* skip to next group; take first "for=..." in group
1023                  * (should be 0 or 1 "for=..." per group, but not trusted) */
1024                 do { j += 4; } while (-1 != offsets[j]);
1025                 ++j;
1026                 continue;
1027             }
1028             j += 4; /*(k, klen, v, vlen come in sets of 4)*/
1029         }
1030     }
1031   #endif
1032 
1033     return HANDLER_GO_ON;
1034 }
1035 
1036 URIHANDLER_FUNC(mod_extforward_uri_handler) {
1037 	plugin_data *p = p_d;
1038 	mod_extforward_patch_config(r, p);
1039 	if (NULL == p->conf.forwarder) return HANDLER_GO_ON;
1040 
1041 	if (p->conf.hap_PROXY_ssl_client_verify) {
1042 		const data_string *ds;
1043 		handler_ctx *hctx = r->con->plugin_ctx[p->id];
1044 		if (NULL != hctx && hctx->ssl_client_verify && NULL != hctx->env
1045 		    && NULL != (ds = (const data_string *)array_get_element_klen(hctx->env, CONST_STR_LEN("SSL_CLIENT_S_DN_CN")))) {
1046 			http_header_env_set(r,
1047 					    CONST_STR_LEN("SSL_CLIENT_VERIFY"),
1048 					    CONST_STR_LEN("SUCCESS"));
1049 			http_header_env_set(r,
1050 					    CONST_STR_LEN("REMOTE_USER"),
1051 					    BUF_PTR_LEN(&ds->value));
1052 			http_header_env_set(r,
1053 					    CONST_STR_LEN("AUTH_TYPE"),
1054 					    CONST_STR_LEN("SSL_CLIENT_VERIFY"));
1055 		} else {
1056 			http_header_env_set(r,
1057 					    CONST_STR_LEN("SSL_CLIENT_VERIFY"),
1058 					    CONST_STR_LEN("NONE"));
1059 		}
1060 	}
1061 
1062 	/* Note: headers are parsed per-request even when using HAProxy PROXY
1063 	 * protocol since Forwarded header might provide additional info and
1064 	 * internal _L_ vars might be set for later use by mod_proxy or others*/
1065 	/*if (p->conf.hap_PROXY) return HANDLER_GO_ON;*/
1066 
1067 	if (NULL == p->conf.headers) return HANDLER_GO_ON;
1068 
1069 	/* Do not reparse headers for same request, e.g. after HANDER_COMEBACK
1070 	 * from mod_rewrite, mod_magnet MAGNET_RESTART_REQUEST, mod_cgi
1071 	 * cgi.local-redir, or gw_backend reconnect.  This has the implication
1072 	 * that mod_magnet and mod_cgi with local-redir should not modify
1073 	 * Forwarded or related headers and expect effects here, though there
1074 	 * are some scenarios with HTTP/2 where headers are reparsed anyway,
1075 	 * due to the loose heuristic checking if request_count changed. */
1076 	handler_ctx *hctx = r->con->plugin_ctx[p->id];
1077 	const int already =
1078 	  (NULL != hctx && !buffer_is_unset(&hctx->saved_remote_addr_buf));
1079 	if (already && hctx->request_count == r->con->request_count)
1080 		return HANDLER_GO_ON;
1081 
1082 	const buffer *forwarded = NULL;
1083 	int is_forwarded_header = 0;
1084 	for (uint32_t k = 0; k < p->conf.headers->used; ++k) {
1085 		const data_string * const ds = (data_string *)p->conf.headers->data[k];
1086 		const buffer * const hdr = &ds->value;
1087 		forwarded = http_header_request_get(r, ds->ext, BUF_PTR_LEN(hdr));
1088 		if (forwarded) {
1089 			is_forwarded_header = (ds->ext == HTTP_HEADER_FORWARDED);
1090 			break;
1091 		}
1092 	}
1093 
1094 	if (forwarded
1095 	    && ((already && r->http_version > HTTP_VERSION_1_1) /*(HTTP_VERSION_2)*/
1096 	        || is_connection_trusted(r->con, p))) {
1097 		return (is_forwarded_header)
1098 		  ? mod_extforward_Forwarded(r, p, forwarded)
1099 		  : mod_extforward_X_Forwarded_For(r, p, forwarded);
1100 	}
1101 	else {
1102 		if (r->conf.log_request_handling) {
1103 			log_error(r->conf.errh, __FILE__, __LINE__,
1104 			  "no forward header found or "
1105 			  "remote address %s is NOT a trusted proxy, skipping",
1106 			  r->con->dst_addr_buf.ptr);
1107 		}
1108 		return HANDLER_GO_ON;
1109 	}
1110 }
1111 
1112 
1113 REQUEST_FUNC(mod_extforward_handle_request_env) {
1114     plugin_data *p = p_d;
1115     handler_ctx *hctx = r->con->plugin_ctx[p->id];
1116     if (NULL == hctx || NULL == hctx->env) return HANDLER_GO_ON;
1117     for (uint32_t i=0; i < hctx->env->used; ++i) {
1118         /* note: replaces values which may have been set by mod_openssl
1119          * (when mod_extforward is listed after mod_openssl in server.modules)*/
1120         data_string *ds = (data_string *)hctx->env->data[i];
1121         http_header_env_set(r, BUF_PTR_LEN(&ds->key), BUF_PTR_LEN(&ds->value));
1122     }
1123     return HANDLER_GO_ON;
1124 }
1125 
1126 
1127 REQUEST_FUNC(mod_extforward_restore) {
1128 	/* Preserve changed addr for lifetime of h2 connection; upstream proxy
1129 	 * should not reuse same h2 connection for requests from different clients*/
1130 	if (r->http_version > HTTP_VERSION_1_1) /*(e.g. HTTP_VERSION_2)*/
1131 		return HANDLER_GO_ON;
1132 	/* XXX: should change this to not occur at request reset,
1133 	*       but instead at connection reset */
1134 	plugin_data *p = p_d;
1135 	connection * const con = r->con;
1136 	handler_ctx *hctx = con->plugin_ctx[p->id];
1137 
1138 	if (!hctx) return HANDLER_GO_ON;
1139 
1140 	if (!buffer_is_unset(&hctx->saved_remote_addr_buf)) {
1141 		con->dst_addr = hctx->saved_remote_addr;
1142 		buffer_move(&con->dst_addr_buf, &hctx->saved_remote_addr_buf);
1143 		/* Now, clean the conf_cond cache, because we may have changed the results of tests */
1144 		config_cond_cache_reset_item(r, COMP_HTTP_REMOTE_IP);
1145 	}
1146 
1147 	if (NULL == hctx->env) {
1148 		handler_ctx_free(hctx);
1149 		con->plugin_ctx[p->id] = NULL;
1150 	}
1151 
1152 	return HANDLER_GO_ON;
1153 }
1154 
1155 
1156 CONNECTION_FUNC(mod_extforward_handle_con_close)
1157 {
1158     plugin_data *p = p_d;
1159     handler_ctx *hctx = con->plugin_ctx[p->id];
1160     if (NULL != hctx) {
1161         if (NULL != hctx->saved_network_read) {
1162             con->network_read = hctx->saved_network_read;
1163         }
1164         if (!buffer_is_unset(&hctx->saved_remote_addr_buf)) {
1165             con->dst_addr = hctx->saved_remote_addr;
1166             buffer_move(&con->dst_addr_buf, &hctx->saved_remote_addr_buf);
1167         }
1168         if (NULL != hctx->env) {
1169             array_free(hctx->env);
1170         }
1171         handler_ctx_free(hctx);
1172         con->plugin_ctx[p->id] = NULL;
1173     }
1174 
1175     return HANDLER_GO_ON;
1176 }
1177 
1178 
1179 static int mod_extforward_network_read (connection *con, chunkqueue *cq, off_t max_bytes);
1180 
1181 CONNECTION_FUNC(mod_extforward_handle_con_accept)
1182 {
1183     request_st * const r = &con->request;
1184     plugin_data *p = p_d;
1185     mod_extforward_patch_config(r, p);
1186     if (!p->conf.hap_PROXY) return HANDLER_GO_ON;
1187     if (NULL == p->conf.forwarder) return HANDLER_GO_ON;
1188     if (is_connection_trusted(con, p)) {
1189         handler_ctx *hctx = handler_ctx_init();
1190         con->plugin_ctx[p->id] = hctx;
1191         hctx->saved_network_read = con->network_read;
1192         con->network_read = mod_extforward_network_read;
1193     }
1194     else {
1195         if (r->conf.log_request_handling) {
1196             log_error(r->conf.errh, __FILE__, __LINE__,
1197               "remote address %s is NOT a trusted proxy, skipping",
1198               con->dst_addr_buf.ptr);
1199         }
1200     }
1201     return HANDLER_GO_ON;
1202 }
1203 
1204 
1205 __attribute_cold__
1206 int mod_extforward_plugin_init(plugin *p);
1207 int mod_extforward_plugin_init(plugin *p) {
1208 	p->version     = LIGHTTPD_VERSION_ID;
1209 	p->name        = "extforward";
1210 
1211 	p->init        = mod_extforward_init;
1212 	p->handle_connection_accept = mod_extforward_handle_con_accept;
1213 	p->handle_uri_raw = mod_extforward_uri_handler;
1214 	p->handle_request_env = mod_extforward_handle_request_env;
1215 	p->handle_request_reset = mod_extforward_restore;
1216 	p->handle_connection_close = mod_extforward_handle_con_close;
1217 	p->set_defaults  = mod_extforward_set_defaults;
1218 	p->cleanup     = mod_extforward_free;
1219 
1220 	return 0;
1221 }
1222 
1223 
1224 
1225 
1226 /* Modified from:
1227  *   http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
1228  *
1229 9. Sample code
1230 
1231 The code below is an example of how a receiver may deal with both versions of
1232 the protocol header for TCP over IPv4 or IPv6. The function is supposed to be
1233 called upon a read event. Addresses may be directly copied into their final
1234 memory location since they're transported in network byte order. The sending
1235 side is even simpler and can easily be deduced from this sample code.
1236  *
1237  */
1238 
1239 union hap_PROXY_hdr {
1240     struct {
1241         char line[108];
1242     } v1;
1243     struct {
1244         uint8_t sig[12];
1245         uint8_t ver_cmd;
1246         uint8_t fam;
1247         uint16_t len;
1248         union {
1249             struct {  /* for TCP/UDP over IPv4, len = 12 */
1250                 uint32_t src_addr;
1251                 uint32_t dst_addr;
1252                 uint16_t src_port;
1253                 uint16_t dst_port;
1254             } ip4;
1255             struct {  /* for TCP/UDP over IPv6, len = 36 */
1256                  uint8_t  src_addr[16];
1257                  uint8_t  dst_addr[16];
1258                  uint16_t src_port;
1259                  uint16_t dst_port;
1260             } ip6;
1261             struct {  /* for AF_UNIX sockets, len = 216 */
1262                  uint8_t src_addr[108];
1263                  uint8_t dst_addr[108];
1264             } unx;
1265         } addr;
1266     } v2;
1267     uint64_t ext[32]; /* 2k (- hdr) for v2 TLV extensions (at least 1536 MTU) */
1268 };
1269 
1270 /*
1271 If the length specified in the PROXY protocol header indicates that additional
1272 bytes are part of the header beyond the address information, a receiver may
1273 choose to skip over and ignore those bytes, or attempt to interpret those
1274 bytes.
1275 
1276 The information in those bytes will be arranged in Type-Length-Value (TLV
1277 vectors) in the following format.  The first byte is the Type of the vector.
1278 The second two bytes represent the length in bytes of the value (not included
1279 the Type and Length bytes), and following the length field is the number of
1280 bytes specified by the length.
1281  */
1282 struct pp2_tlv {
1283     uint8_t type;
1284     uint8_t length_hi;
1285     uint8_t length_lo;
1286     /*uint8_t value[0];*//* C99 zero-length array */
1287 };
1288 
1289 /*
1290 The following types have already been registered for the <type> field :
1291  */
1292 
1293 #define PP2_TYPE_ALPN             0x01
1294 #define PP2_TYPE_AUTHORITY        0x02
1295 #define PP2_TYPE_CRC32C           0x03
1296 #define PP2_TYPE_NOOP             0x04
1297 #define PP2_TYPE_UNIQUE_ID        0x05
1298 #define PP2_TYPE_SSL              0x20
1299 #define PP2_SUBTYPE_SSL_VERSION   0x21
1300 #define PP2_SUBTYPE_SSL_CN        0x22
1301 #define PP2_SUBTYPE_SSL_CIPHER    0x23
1302 #define PP2_SUBTYPE_SSL_SIG_ALG   0x24
1303 #define PP2_SUBTYPE_SSL_KEY_ALG   0x25
1304 #define PP2_TYPE_NETNS            0x30
1305 
1306 /*
1307 For the type PP2_TYPE_SSL, the value is itselv a defined like this :
1308  */
1309 
1310 struct pp2_tlv_ssl {
1311     uint8_t  client;
1312     uint32_t verify;
1313     /*struct pp2_tlv sub_tlv[0];*//* C99 zero-length array */
1314 };
1315 
1316 /*
1317 And the <client> field is made of a bit field from the following values,
1318 indicating which element is present :
1319  */
1320 
1321 #define PP2_CLIENT_SSL            0x01
1322 #define PP2_CLIENT_CERT_CONN      0x02
1323 #define PP2_CLIENT_CERT_SESS      0x04
1324 
1325 
1326 
1327 
1328 #ifndef MSG_DONTWAIT
1329 #define MSG_DONTWAIT 0
1330 #endif
1331 #ifndef MSG_NOSIGNAL
1332 #define MSG_NOSIGNAL 0
1333 #endif
1334 
1335 /* returns 0 if needs to poll, <0 upon error or >0 is protocol vers (success) */
1336 static int hap_PROXY_recv (const int fd, union hap_PROXY_hdr * const hdr, const int family, const int so_type)
1337 {
1338     static const char v2sig[12] =
1339         "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A";
1340 
1341     ssize_t ret;
1342     size_t sz;
1343     int ver;
1344 
1345     do {
1346         ret = recv(fd, hdr, sizeof(*hdr), MSG_PEEK|MSG_DONTWAIT|MSG_NOSIGNAL);
1347     } while (-1 == ret && errno == EINTR);
1348 
1349     if (-1 == ret)
1350         return (errno == EAGAIN
1351                 #ifdef EWOULDBLOCK
1352                 #if EAGAIN != EWOULDBLOCK
1353                 || errno == EWOULDBLOCK
1354                 #endif
1355                 #endif
1356                ) ? 0 : -1;
1357 
1358     if (ret >= 16 && 0 == memcmp(&hdr->v2, v2sig, 12)
1359         && (hdr->v2.ver_cmd & 0xF0) == 0x20) {
1360         ver = 2;
1361         sz = 16 + (size_t)ntohs(hdr->v2.len);
1362         if ((size_t)ret < sz)
1363             return -2; /* truncated or too large header */
1364 
1365         switch (hdr->v2.ver_cmd & 0xF) {
1366           case 0x01: break; /* PROXY command */
1367           case 0x00: break; /* LOCAL command */
1368           default:   return -2; /* not a supported command */
1369         }
1370     }
1371     else if (ret >= 8 && 0 == memcmp(hdr->v1.line, "PROXY", 5)) {
1372         const char *end = memchr(hdr->v1.line, '\r', ret - 1);
1373         if (!end || end[1] != '\n')
1374             return -2; /* partial or invalid header */
1375         ver = 1;
1376         sz = (size_t)(end + 2 - hdr->v1.line); /* skip header + CRLF */
1377     }
1378     else {
1379         /* Wrong protocol */
1380         return -2;
1381     }
1382 
1383     /* we need to consume the appropriate amount of data from the socket
1384      * (overwrites existing contents of hdr with same data) */
1385     UNUSED(family);
1386     UNUSED(so_type);
1387     do {
1388       #if defined(MSG_TRUNC) && defined(__linux__)
1389         if ((family==AF_INET || family==AF_INET6) && so_type == SOCK_STREAM) {
1390             ret = recv(fd, hdr, sz, MSG_TRUNC|MSG_DONTWAIT|MSG_NOSIGNAL);
1391             if (ret >= 0 || errno != EINVAL) continue;
1392         }
1393       #endif
1394         ret = recv(fd, hdr, sz, MSG_DONTWAIT|MSG_NOSIGNAL);
1395     } while (-1 == ret && errno == EINTR);
1396     if (ret < 0) return -1;
1397     if (ret != (ssize_t)sz) {
1398         errno = EIO; /*(partial read; valid but unexpected; not handled)*/
1399         return -1;
1400     }
1401     if (1 == ver) hdr->v1.line[sz-2] = '\0'; /*terminate str to ease parsing*/
1402     return ver;
1403 }
1404 
1405 
1406 __attribute_pure__
1407 static int mod_extforward_str_to_port (const char * const s)
1408 {
1409     /*(more strict than strtol(); digits only)*/
1410     int port = 0;
1411     for (int i = 0; i < 5; ++i, port *= 10) {
1412         if (!light_isdigit(s[i])) return -1;
1413         port += (s[i] - '0');
1414         if (s[i+1] == '\0') return port;
1415     }
1416     return -1;
1417 }
1418 
1419 /* coverity[-tainted_data_sink: arg-1] */
1420 static int mod_extforward_hap_PROXY_v1 (connection * const con,
1421                                         union hap_PROXY_hdr * const hdr)
1422 {
1423   #ifdef __COVERITY__
1424     __coverity_tainted_data_sink__(hdr);
1425   #endif
1426 
1427     /* samples
1428      *   "PROXY TCP4 255.255.255.255 255.255.255.255 65535 65535\r\n"
1429      *   "PROXY TCP6 ffff:f...f:ffff ffff:f...f:ffff 65535 65535\r\n"
1430      *   "PROXY UNKNOWN\r\n"
1431      *   "PROXY UNKNOWN ffff:f...f:ffff ffff:f...f:ffff 65535 65535\r\n"
1432      */
1433     char *s = hdr->v1.line + sizeof("PROXY")-1; /*checked in hap_PROXY_recv()*/
1434     char *src_addr, *dst_addr, *src_port, *dst_port;
1435     int family;
1436     int src_lport, dst_lport;
1437     if (*s != ' ') return -1;
1438     ++s;
1439     if (s[0] == 'T' && s[1] == 'C' && s[2] == 'P' && s[4] == ' ') {
1440         if (s[3] == '4') {
1441             family = AF_INET;
1442         } else if (s[3] == '6') {
1443             family = AF_INET6;
1444         }
1445         else {
1446             return -1;
1447         }
1448         s += 5;
1449     }
1450     else if (0 == memcmp(s, "UNKNOWN", sizeof("UNKNOWN")-1)
1451              && (s[7] == '\0' || s[7] == ' ')) {
1452         return 0;     /* keep local connection address */
1453     }
1454     else {
1455         return -1;
1456     }
1457 
1458     /*(strsep() should be fairly portable, but is not standard)*/
1459     src_addr = s;
1460     dst_addr = strchr(src_addr, ' ');
1461     if (NULL == dst_addr) return -1;
1462     *dst_addr++ = '\0';
1463     src_port = strchr(dst_addr, ' ');
1464     if (NULL == src_port) return -1;
1465     *src_port++ = '\0';
1466     dst_port = strchr(src_port, ' ');
1467     if (NULL == dst_port) return -1;
1468     *dst_port++ = '\0';
1469 
1470     src_lport = mod_extforward_str_to_port(src_port);
1471     if (src_lport <= 0) return -1;
1472     dst_lport = mod_extforward_str_to_port(dst_port);
1473     if (dst_lport <= 0) return -1;
1474 
1475     if (1 != sock_addr_inet_pton(&con->dst_addr,
1476                                  src_addr, family, (unsigned short)src_lport))
1477         return -1;
1478     /* Forwarded by=... could be saved here.
1479      * (see additional comments in mod_extforward_hap_PROXY_v2()) */
1480 
1481     /* re-parse addr to string to normalize
1482      * (instead of trusting PROXY to provide canonicalized src_addr string)
1483      * (should prefer PROXY v2 protocol if concerned about performance) */
1484     sock_addr_inet_ntop_copy_buffer(&con->dst_addr_buf, &con->dst_addr);
1485 
1486     return 0;
1487 }
1488 
1489 
1490 /* coverity[-tainted_data_sink: arg-1] */
1491 static int mod_extforward_hap_PROXY_v2 (connection * const con,
1492                                         union hap_PROXY_hdr * const hdr)
1493 {
1494   #ifdef __COVERITY__
1495     __coverity_tainted_data_sink__(hdr);
1496   #endif
1497 
1498     /* If HAProxy-PROXY protocol used, then lighttpd acts as transparent proxy,
1499      * masquerading as servicing the client IP provided in by HAProxy-PROXY hdr.
1500      * The connecting con->dst_addr and con->dst_addr_buf are not saved here,
1501      * so that info is lost unless getsockname() and getpeername() are used.
1502      * One result is that mod_proxy will use the masqueraded IP instead of the
1503      * actual IP when updated Forwarded and X-Forwarded-For (but if actual
1504      * connection IPs needed, better to save the info here rather than use
1505      * syscalls to retrieve the info later).
1506      * (Exception: con->dst_addr can be further changed if mod_extforward parses
1507      *  Forwarded or X-Forwarded-For request headers later, after request headers
1508      *  have been received.)
1509      */
1510 
1511     /* Forwarded by=... could be saved here.  The by param is for backends to be
1512      * able to construct URIs for that interface (interface on server which
1513      * received request and made PROXY connection here), though that server
1514      * should provide that information in updated Forwarded or X-Forwarded-For
1515      * HTTP headers */
1516     /*struct sockaddr_storage by;*/
1517 
1518     /* Addresses provided by HAProxy-PROXY protocol are in network byte order.
1519      * Note: addr info is not validated, so do not accept HAProxy-PROXY
1520      * protocol from untrusted servers.  For example, untrusted servers from
1521      * which HAProxy-PROXY protocol is accepted (don't do that) could pretend
1522      * to be from the internal network and might thereby bypass security policy.
1523      */
1524 
1525     /* (Clear con->dst_addr with memset() in case actual and proxies IPs
1526      *  are different domains, e.g. one is IPv4 and the other is IPv6) */
1527 
1528     struct pp2_tlv *tlv;
1529     uint32_t sz = ntohs(hdr->v2.len);
1530     uint32_t len = 0;
1531 
1532     switch (hdr->v2.ver_cmd & 0xF) {
1533       case 0x01: break;    /* PROXY command */
1534       case 0x00: return  0;/* LOCAL command; keep local connection address */
1535       default:   return -1;/* should not happen; validated in hap_PROXY_recv()*/
1536     }
1537 
1538     /* PROXY command */
1539 
1540     switch (hdr->v2.fam) {
1541       case 0x11:  /* TCPv4 */
1542         sock_addr_assign(&con->dst_addr, AF_INET, hdr->v2.addr.ip4.src_port,
1543                                                  &hdr->v2.addr.ip4.src_addr);
1544         sock_addr_inet_ntop_copy_buffer(&con->dst_addr_buf, &con->dst_addr);
1545        #if 0
1546         ((struct sockaddr_in *)&by)->sin_family = AF_INET;
1547         ((struct sockaddr_in *)&by)->sin_addr.s_addr =
1548             hdr->v2.addr.ip4.dst_addr;
1549         ((struct sockaddr_in *)&by)->sin_port =
1550             hdr->v2.addr.ip4.dst_port;
1551        #endif
1552         len = (uint32_t)sizeof(hdr->v2.addr.ip4);
1553         break;
1554      #ifdef HAVE_IPV6
1555       case 0x21:  /* TCPv6 */
1556         sock_addr_assign(&con->dst_addr, AF_INET6, hdr->v2.addr.ip6.src_port,
1557                                                   &hdr->v2.addr.ip6.src_addr);
1558         sock_addr_inet_ntop_copy_buffer(&con->dst_addr_buf, &con->dst_addr);
1559        #if 0
1560         ((struct sockaddr_in6 *)&by)->sin6_family = AF_INET6;
1561         memcpy(&((struct sockaddr_in6 *)&by)->sin6_addr,
1562             hdr->v2.addr.ip6.dst_addr, 16);
1563         ((struct sockaddr_in6 *)&by)->sin6_port =
1564             hdr->v2.addr.ip6.dst_port;
1565        #endif
1566         len = (uint32_t)sizeof(hdr->v2.addr.ip6);
1567         break;
1568      #endif
1569      #ifdef HAVE_SYS_UN_H
1570       case 0x31:  /* UNIX domain socket */
1571         {
1572             char *src_addr = (char *)hdr->v2.addr.unx.src_addr;
1573             char *z = memchr(src_addr, '\0', sizeof(hdr->v2.addr.unx.src_addr));
1574             if (NULL == z) return -1; /* invalid addr; too long */
1575             len = (uint32_t)(z - src_addr);
1576             /*if (0 == len) return -1;*//* abstract socket not supported; err?*/
1577             if (0 != sock_addr_assign(&con->dst_addr, AF_UNIX, 0, src_addr))
1578                 return -1; /* invalid addr; too long */
1579             buffer_copy_string_len(&con->dst_addr_buf, src_addr, len);
1580         }
1581        #if 0 /*(dst_addr should be identical to src_addr for AF_UNIX)*/
1582         ((struct sockaddr_un *)&by)->sun_family = AF_UNIX;
1583         memcpy(&((struct sockaddr_un *)&by)->sun_path,
1584             hdr->v2.addr.unx.dst_addr, 108);
1585        #endif
1586         len = (uint32_t)sizeof(hdr->v2.addr.unx);
1587         break;
1588      #endif
1589       default:    /* keep local connection address; unsupported protocol */
1590         return 0;
1591     }
1592 
1593     /* (optional) Type-Length-Value (TLV vectors) follow addresses */
1594 
1595     if (3 + len > sz) return 0;
1596 
1597     handler_ctx * const hctx =
1598       con->plugin_ctx[mod_extforward_plugin_data_singleton->id];
1599     tlv = (struct pp2_tlv *)((char *)hdr + 16);
1600     for (sz -= len, len -= 3; sz >= 3; sz -= 3 + len) {
1601         tlv = (struct pp2_tlv *)((char *)tlv + 3 + len);
1602         len = ((uint32_t)tlv->length_hi << 8) | tlv->length_lo;
1603         if (3 + len > sz) break; /*(invalid TLV)*/
1604         const char *k;
1605         uint32_t klen;
1606         switch (tlv->type) {
1607          #if 0 /*(not implemented here)*/
1608           case PP2_TYPE_ALPN:
1609           case PP2_TYPE_AUTHORITY:
1610           case PP2_TYPE_CRC32C:
1611          #endif
1612           case PP2_TYPE_SSL: {
1613             if (len < 5) continue;
1614             static const uint32_t zero = 0;
1615             struct pp2_tlv_ssl *tlv_ssl =
1616               (struct pp2_tlv_ssl *)(void *)((char *)tlv+3);
1617             struct pp2_tlv *subtlv = tlv;
1618             if (tlv_ssl->client & PP2_CLIENT_SSL) {
1619                 con->proto_default_port = 443; /* "https" */
1620             }
1621             if ((tlv_ssl->client & (PP2_CLIENT_CERT_CONN|PP2_CLIENT_CERT_SESS))
1622                 && 0 == memcmp(&tlv_ssl->verify, &zero, 4)) { /* misaligned */
1623                 hctx->ssl_client_verify = 1;
1624             }
1625             if (len < 5 + 3) continue;
1626             if (NULL == hctx->env) hctx->env = array_init(8);
1627             for (uint32_t subsz = len-5, n = 5; subsz >= 3; subsz -= 3 + n) {
1628                 subtlv = (struct pp2_tlv *)((char *)subtlv + 3 + n);
1629                 n = ((uint32_t)subtlv->length_hi << 8) | subtlv->length_lo;
1630                 if (3 + n > subsz) break; /*(invalid TLV)*/
1631                 switch (subtlv->type) {
1632                   case PP2_SUBTYPE_SSL_VERSION:
1633                     k = "SSL_PROTOCOL";
1634                     klen = sizeof("SSL_PROTOCOL")-1;
1635                     break;
1636                   case PP2_SUBTYPE_SSL_CN:
1637                     /* (tlv_ssl->client & PP2_CLIENT_CERT_CONN)
1638                      *   or
1639                      * (tlv_ssl->client & PP2_CLIENT_CERT_SESS) */
1640                     k = "SSL_CLIENT_S_DN_CN";
1641                     klen = sizeof("SSL_CLIENT_S_DN_CN")-1;
1642                     break;
1643                   case PP2_SUBTYPE_SSL_CIPHER:
1644                     k = "SSL_CIPHER";
1645                     klen = sizeof("SSL_CIPHER")-1;
1646                     break;
1647                   case PP2_SUBTYPE_SSL_SIG_ALG:
1648                     k = "SSL_SERVER_A_SIG";
1649                     klen = sizeof("SSL_SERVER_A_SIG")-1;
1650                     break;
1651                   case PP2_SUBTYPE_SSL_KEY_ALG:
1652                     k = "SSL_SERVER_A_KEY";
1653                     klen = sizeof("SSL_SERVER_A_KEY")-1;
1654                     break;
1655                   default:
1656                     continue;
1657                 }
1658                 array_set_key_value(hctx->env, k, klen, (char *)subtlv+3, n);
1659             }
1660             continue;
1661           }
1662           case PP2_TYPE_UNIQUE_ID:
1663             k = "PP2_UNIQUE_ID";
1664             klen = sizeof("PP2_UNIQUE_ID")-1;
1665             break;
1666          #if 0 /*(not implemented here)*/
1667           case PP2_TYPE_NETNS:
1668          #endif
1669           /*case PP2_TYPE_NOOP:*//* no-op */
1670           default:
1671             continue;
1672         }
1673         if (NULL == hctx->env) hctx->env = array_init(8);
1674         array_set_key_value(hctx->env, k, klen, (char *)tlv+3, len);
1675     }
1676 
1677     return 0;
1678 }
1679 
1680 
1681 static int mod_extforward_network_read (connection *con,
1682                                         chunkqueue *cq, off_t max_bytes)
1683 {
1684     /* XXX: when using hap-PROXY protocol, currently avoid overhead of setting
1685      * _L_ environment variables for mod_proxy to accurately set Forwarded hdr
1686      * In the future, might add config switch to enable doing this extra work */
1687 
1688     union hap_PROXY_hdr hdr;
1689     log_error_st *errh;
1690     const int family = sock_addr_get_family(&con->dst_addr);
1691     int rc = hap_PROXY_recv(con->fd, &hdr, family, SOCK_STREAM);
1692     switch (rc) {
1693       case  2: rc = mod_extforward_hap_PROXY_v2(con, &hdr); break;
1694       case  1: rc = mod_extforward_hap_PROXY_v1(con, &hdr); break;
1695       case  0: return  0; /*(errno == EAGAIN || errno == EWOULDBLOCK)*/
1696       case -1: errh = con->srv->errh;
1697                log_perror(errh,__FILE__,__LINE__,"hap-PROXY recv()");
1698                rc = -1; break;
1699       case -2: errh = con->srv->errh;
1700                log_error(errh,__FILE__,__LINE__,
1701                  "hap-PROXY proto received invalid/unsupported request");
1702                __attribute_fallthrough__
1703       default: rc = -1; break;
1704     }
1705 
1706     handler_ctx *hctx =
1707       con->plugin_ctx[mod_extforward_plugin_data_singleton->id];
1708     con->network_read = hctx->saved_network_read;
1709     hctx->saved_network_read = NULL;
1710     return (0 == rc) ? con->network_read(con, cq, max_bytes) : rc;
1711 }
1712