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