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