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