xref: /lighttpd1.4/src/mod_extforward.c (revision b82d7b8a)
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 __attribute_cold__
1211 int mod_extforward_plugin_init(plugin *p);
1212 int mod_extforward_plugin_init(plugin *p) {
1213 	p->version     = LIGHTTPD_VERSION_ID;
1214 	p->name        = "extforward";
1215 
1216 	p->init        = mod_extforward_init;
1217 	p->handle_connection_accept = mod_extforward_handle_con_accept;
1218 	p->handle_uri_raw = mod_extforward_uri_handler;
1219 	p->handle_request_env = mod_extforward_handle_request_env;
1220 	p->handle_request_reset = mod_extforward_restore;
1221 	p->handle_connection_close = mod_extforward_handle_con_close;
1222 	p->set_defaults  = mod_extforward_set_defaults;
1223 	p->cleanup     = mod_extforward_free;
1224 
1225 	return 0;
1226 }
1227 
1228 
1229 
1230 
1231 /* Modified from:
1232  *   http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
1233  *
1234 9. Sample code
1235 
1236 The code below is an example of how a receiver may deal with both versions of
1237 the protocol header for TCP over IPv4 or IPv6. The function is supposed to be
1238 called upon a read event. Addresses may be directly copied into their final
1239 memory location since they're transported in network byte order. The sending
1240 side is even simpler and can easily be deduced from this sample code.
1241  *
1242  */
1243 
1244 union hap_PROXY_hdr {
1245     struct {
1246         char line[108];
1247     } v1;
1248     struct {
1249         uint8_t sig[12];
1250         uint8_t ver_cmd;
1251         uint8_t fam;
1252         uint16_t len;
1253         union {
1254             struct {  /* for TCP/UDP over IPv4, len = 12 */
1255                 uint32_t src_addr;
1256                 uint32_t dst_addr;
1257                 uint16_t src_port;
1258                 uint16_t dst_port;
1259             } ip4;
1260             struct {  /* for TCP/UDP over IPv6, len = 36 */
1261                  uint8_t  src_addr[16];
1262                  uint8_t  dst_addr[16];
1263                  uint16_t src_port;
1264                  uint16_t dst_port;
1265             } ip6;
1266             struct {  /* for AF_UNIX sockets, len = 216 */
1267                  uint8_t src_addr[108];
1268                  uint8_t dst_addr[108];
1269             } unx;
1270         } addr;
1271     } v2;
1272     uint64_t ext[32]; /* 2k (- hdr) for v2 TLV extensions (at least 1536 MTU) */
1273 };
1274 
1275 /*
1276 If the length specified in the PROXY protocol header indicates that additional
1277 bytes are part of the header beyond the address information, a receiver may
1278 choose to skip over and ignore those bytes, or attempt to interpret those
1279 bytes.
1280 
1281 The information in those bytes will be arranged in Type-Length-Value (TLV
1282 vectors) in the following format.  The first byte is the Type of the vector.
1283 The second two bytes represent the length in bytes of the value (not included
1284 the Type and Length bytes), and following the length field is the number of
1285 bytes specified by the length.
1286  */
1287 struct pp2_tlv {
1288     uint8_t type;
1289     uint8_t length_hi;
1290     uint8_t length_lo;
1291     /*uint8_t value[0];*//* C99 zero-length array */
1292 };
1293 
1294 /*
1295 The following types have already been registered for the <type> field :
1296  */
1297 
1298 #define PP2_TYPE_ALPN             0x01
1299 #define PP2_TYPE_AUTHORITY        0x02
1300 #define PP2_TYPE_CRC32C           0x03
1301 #define PP2_TYPE_NOOP             0x04
1302 #define PP2_TYPE_UNIQUE_ID        0x05
1303 #define PP2_TYPE_SSL              0x20
1304 #define PP2_SUBTYPE_SSL_VERSION   0x21
1305 #define PP2_SUBTYPE_SSL_CN        0x22
1306 #define PP2_SUBTYPE_SSL_CIPHER    0x23
1307 #define PP2_SUBTYPE_SSL_SIG_ALG   0x24
1308 #define PP2_SUBTYPE_SSL_KEY_ALG   0x25
1309 #define PP2_TYPE_NETNS            0x30
1310 
1311 /*
1312 For the type PP2_TYPE_SSL, the value is itselv a defined like this :
1313  */
1314 
1315 struct pp2_tlv_ssl {
1316     uint8_t  client;
1317     uint32_t verify;
1318     /*struct pp2_tlv sub_tlv[0];*//* C99 zero-length array */
1319 };
1320 
1321 /*
1322 And the <client> field is made of a bit field from the following values,
1323 indicating which element is present :
1324  */
1325 
1326 #define PP2_CLIENT_SSL            0x01
1327 #define PP2_CLIENT_CERT_CONN      0x02
1328 #define PP2_CLIENT_CERT_SESS      0x04
1329 
1330 
1331 
1332 
1333 #ifndef MSG_DONTWAIT
1334 #define MSG_DONTWAIT 0
1335 #endif
1336 #ifndef MSG_NOSIGNAL
1337 #define MSG_NOSIGNAL 0
1338 #endif
1339 
1340 /* returns 0 if needs to poll, <0 upon error or >0 is protocol vers (success) */
1341 static int hap_PROXY_recv (const int fd, union hap_PROXY_hdr * const hdr, const int family, const int so_type)
1342 {
1343     static const char v2sig[12] =
1344         "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A";
1345 
1346     ssize_t ret;
1347     size_t sz;
1348     int ver;
1349 
1350     do {
1351         ret = recv(fd, hdr, sizeof(*hdr), MSG_PEEK|MSG_DONTWAIT|MSG_NOSIGNAL);
1352     } while (-1 == ret && errno == EINTR);
1353 
1354     if (-1 == ret)
1355         return (errno == EAGAIN
1356                 #ifdef EWOULDBLOCK
1357                 #if EAGAIN != EWOULDBLOCK
1358                 || errno == EWOULDBLOCK
1359                 #endif
1360                 #endif
1361                ) ? 0 : -1;
1362 
1363     if (ret >= 16 && 0 == memcmp(&hdr->v2, v2sig, 12)
1364         && (hdr->v2.ver_cmd & 0xF0) == 0x20) {
1365         ver = 2;
1366         sz = 16 + (size_t)ntohs(hdr->v2.len);
1367         if ((size_t)ret < sz)
1368             return -2; /* truncated or too large header */
1369 
1370         switch (hdr->v2.ver_cmd & 0xF) {
1371           case 0x01: break; /* PROXY command */
1372           case 0x00: break; /* LOCAL command */
1373           default:   return -2; /* not a supported command */
1374         }
1375     }
1376     else if (ret >= 8 && 0 == memcmp(hdr->v1.line, "PROXY", 5)) {
1377         const char *end = memchr(hdr->v1.line, '\r', ret - 1);
1378         if (!end || end[1] != '\n')
1379             return -2; /* partial or invalid header */
1380         ver = 1;
1381         sz = (size_t)(end + 2 - hdr->v1.line); /* skip header + CRLF */
1382     }
1383     else {
1384         /* Wrong protocol */
1385         return -2;
1386     }
1387 
1388     /* we need to consume the appropriate amount of data from the socket
1389      * (overwrites existing contents of hdr with same data) */
1390     UNUSED(family);
1391     UNUSED(so_type);
1392     do {
1393       #if defined(MSG_TRUNC) && defined(__linux__)
1394         if ((family==AF_INET || family==AF_INET6) && so_type == SOCK_STREAM) {
1395             ret = recv(fd, hdr, sz, MSG_TRUNC|MSG_DONTWAIT|MSG_NOSIGNAL);
1396             if (ret >= 0 || errno != EINVAL) continue;
1397         }
1398       #endif
1399         ret = recv(fd, hdr, sz, MSG_DONTWAIT|MSG_NOSIGNAL);
1400     } while (-1 == ret && errno == EINTR);
1401     if (ret < 0) return -1;
1402     if (ret != (ssize_t)sz) {
1403         errno = EIO; /*(partial read; valid but unexpected; not handled)*/
1404         return -1;
1405     }
1406     if (1 == ver) hdr->v1.line[sz-2] = '\0'; /*terminate str to ease parsing*/
1407     return ver;
1408 }
1409 
1410 
1411 __attribute_pure__
1412 static int mod_extforward_str_to_port (const char * const s)
1413 {
1414     /*(more strict than strtol(); digits only)*/
1415     int port = 0;
1416     for (int i = 0; i < 5; ++i, port *= 10) {
1417         if (!light_isdigit(s[i])) return -1;
1418         port += (s[i] - '0');
1419         if (s[i+1] == '\0') return port;
1420     }
1421     return -1;
1422 }
1423 
1424 /* coverity[-tainted_data_sink: arg-1] */
1425 static int mod_extforward_hap_PROXY_v1 (connection * const con,
1426                                         union hap_PROXY_hdr * const hdr)
1427 {
1428   #ifdef __COVERITY__
1429     __coverity_tainted_data_sink__(hdr);
1430   #endif
1431 
1432     /* samples
1433      *   "PROXY TCP4 255.255.255.255 255.255.255.255 65535 65535\r\n"
1434      *   "PROXY TCP6 ffff:f...f:ffff ffff:f...f:ffff 65535 65535\r\n"
1435      *   "PROXY UNKNOWN\r\n"
1436      *   "PROXY UNKNOWN ffff:f...f:ffff ffff:f...f:ffff 65535 65535\r\n"
1437      */
1438     char *s = hdr->v1.line + sizeof("PROXY")-1; /*checked in hap_PROXY_recv()*/
1439     char *src_addr, *dst_addr, *src_port, *dst_port;
1440     int family;
1441     int src_lport, dst_lport;
1442     if (*s != ' ') return -1;
1443     ++s;
1444     if (s[0] == 'T' && s[1] == 'C' && s[2] == 'P' && s[4] == ' ') {
1445         if (s[3] == '4') {
1446             family = AF_INET;
1447         } else if (s[3] == '6') {
1448             family = AF_INET6;
1449         }
1450         else {
1451             return -1;
1452         }
1453         s += 5;
1454     }
1455     else if (0 == memcmp(s, "UNKNOWN", sizeof("UNKNOWN")-1)
1456              && (s[7] == '\0' || s[7] == ' ')) {
1457         return 0;     /* keep local connection address */
1458     }
1459     else {
1460         return -1;
1461     }
1462 
1463     /*(strsep() should be fairly portable, but is not standard)*/
1464     src_addr = s;
1465     dst_addr = strchr(src_addr, ' ');
1466     if (NULL == dst_addr) return -1;
1467     *dst_addr++ = '\0';
1468     src_port = strchr(dst_addr, ' ');
1469     if (NULL == src_port) return -1;
1470     *src_port++ = '\0';
1471     dst_port = strchr(src_port, ' ');
1472     if (NULL == dst_port) return -1;
1473     *dst_port++ = '\0';
1474 
1475     src_lport = mod_extforward_str_to_port(src_port);
1476     if (src_lport <= 0) return -1;
1477     dst_lport = mod_extforward_str_to_port(dst_port);
1478     if (dst_lport <= 0) return -1;
1479 
1480     if (1 != sock_addr_inet_pton(&con->dst_addr,
1481                                  src_addr, family, (unsigned short)src_lport))
1482         return -1;
1483     /* Forwarded by=... could be saved here.
1484      * (see additional comments in mod_extforward_hap_PROXY_v2()) */
1485 
1486     /* re-parse addr to string to normalize
1487      * (instead of trusting PROXY to provide canonicalized src_addr string)
1488      * (should prefer PROXY v2 protocol if concerned about performance) */
1489     sock_addr_inet_ntop_copy_buffer(&con->dst_addr_buf, &con->dst_addr);
1490 
1491     return 0;
1492 }
1493 
1494 
1495 /* coverity[-tainted_data_sink: arg-1] */
1496 static int mod_extforward_hap_PROXY_v2 (connection * const con,
1497                                         union hap_PROXY_hdr * const hdr)
1498 {
1499   #ifdef __COVERITY__
1500     __coverity_tainted_data_sink__(hdr);
1501   #endif
1502 
1503     /* If HAProxy-PROXY protocol used, then lighttpd acts as transparent proxy,
1504      * masquerading as servicing the client IP provided in by HAProxy-PROXY hdr.
1505      * The connecting con->dst_addr and con->dst_addr_buf are not saved here,
1506      * so that info is lost unless getsockname() and getpeername() are used.
1507      * One result is that mod_proxy will use the masqueraded IP instead of the
1508      * actual IP when updated Forwarded and X-Forwarded-For (but if actual
1509      * connection IPs needed, better to save the info here rather than use
1510      * syscalls to retrieve the info later).
1511      * (Exception: con->dst_addr can be further changed if mod_extforward parses
1512      *  Forwarded or X-Forwarded-For request headers later, after request headers
1513      *  have been received.)
1514      */
1515 
1516     /* Forwarded by=... could be saved here.  The by param is for backends to be
1517      * able to construct URIs for that interface (interface on server which
1518      * received request and made PROXY connection here), though that server
1519      * should provide that information in updated Forwarded or X-Forwarded-For
1520      * HTTP headers */
1521     /*struct sockaddr_storage by;*/
1522 
1523     /* Addresses provided by HAProxy-PROXY protocol are in network byte order.
1524      * Note: addr info is not validated, so do not accept HAProxy-PROXY
1525      * protocol from untrusted servers.  For example, untrusted servers from
1526      * which HAProxy-PROXY protocol is accepted (don't do that) could pretend
1527      * to be from the internal network and might thereby bypass security policy.
1528      */
1529 
1530     /* (Clear con->dst_addr with memset() in case actual and proxies IPs
1531      *  are different domains, e.g. one is IPv4 and the other is IPv6) */
1532 
1533     struct pp2_tlv *tlv;
1534     uint32_t sz = ntohs(hdr->v2.len);
1535     uint32_t len = 0;
1536 
1537     switch (hdr->v2.ver_cmd & 0xF) {
1538       case 0x01: break;    /* PROXY command */
1539       case 0x00: return  0;/* LOCAL command; keep local connection address */
1540       default:   return -1;/* should not happen; validated in hap_PROXY_recv()*/
1541     }
1542 
1543     /* PROXY command */
1544 
1545     switch (hdr->v2.fam) {
1546       case 0x11:  /* TCPv4 */
1547         sock_addr_assign(&con->dst_addr, AF_INET, hdr->v2.addr.ip4.src_port,
1548                                                  &hdr->v2.addr.ip4.src_addr);
1549         sock_addr_inet_ntop_copy_buffer(&con->dst_addr_buf, &con->dst_addr);
1550        #if 0
1551         ((struct sockaddr_in *)&by)->sin_family = AF_INET;
1552         ((struct sockaddr_in *)&by)->sin_addr.s_addr =
1553             hdr->v2.addr.ip4.dst_addr;
1554         ((struct sockaddr_in *)&by)->sin_port =
1555             hdr->v2.addr.ip4.dst_port;
1556        #endif
1557         len = (uint32_t)sizeof(hdr->v2.addr.ip4);
1558         break;
1559      #ifdef HAVE_IPV6
1560       case 0x21:  /* TCPv6 */
1561         sock_addr_assign(&con->dst_addr, AF_INET6, hdr->v2.addr.ip6.src_port,
1562                                                   &hdr->v2.addr.ip6.src_addr);
1563         sock_addr_inet_ntop_copy_buffer(&con->dst_addr_buf, &con->dst_addr);
1564        #if 0
1565         ((struct sockaddr_in6 *)&by)->sin6_family = AF_INET6;
1566         memcpy(&((struct sockaddr_in6 *)&by)->sin6_addr,
1567             hdr->v2.addr.ip6.dst_addr, 16);
1568         ((struct sockaddr_in6 *)&by)->sin6_port =
1569             hdr->v2.addr.ip6.dst_port;
1570        #endif
1571         len = (uint32_t)sizeof(hdr->v2.addr.ip6);
1572         break;
1573      #endif
1574      #ifdef HAVE_SYS_UN_H
1575       case 0x31:  /* UNIX domain socket */
1576         {
1577             char *src_addr = (char *)hdr->v2.addr.unx.src_addr;
1578             char *z = memchr(src_addr, '\0', sizeof(hdr->v2.addr.unx.src_addr));
1579             if (NULL == z) return -1; /* invalid addr; too long */
1580             len = (uint32_t)(z - src_addr);
1581             /*if (0 == len) return -1;*//* abstract socket not supported; err?*/
1582             if (0 != sock_addr_assign(&con->dst_addr, AF_UNIX, 0, src_addr))
1583                 return -1; /* invalid addr; too long */
1584             buffer_copy_string_len(&con->dst_addr_buf, src_addr, len);
1585         }
1586        #if 0 /*(dst_addr should be identical to src_addr for AF_UNIX)*/
1587         ((struct sockaddr_un *)&by)->sun_family = AF_UNIX;
1588         memcpy(&((struct sockaddr_un *)&by)->sun_path,
1589             hdr->v2.addr.unx.dst_addr, 108);
1590        #endif
1591         len = (uint32_t)sizeof(hdr->v2.addr.unx);
1592         break;
1593      #endif
1594       default:    /* keep local connection address; unsupported protocol */
1595         return 0;
1596     }
1597 
1598     /* (optional) Type-Length-Value (TLV vectors) follow addresses */
1599 
1600     if (3 + len > sz) return 0;
1601 
1602     handler_ctx * const hctx =
1603       con->plugin_ctx[mod_extforward_plugin_data_singleton->id];
1604     tlv = (struct pp2_tlv *)((char *)hdr + 16);
1605     for (sz -= len, len -= 3; sz >= 3; sz -= 3 + len) {
1606         tlv = (struct pp2_tlv *)((char *)tlv + 3 + len);
1607         len = ((uint32_t)tlv->length_hi << 8) | tlv->length_lo;
1608         if (3 + len > sz) break; /*(invalid TLV)*/
1609         const char *k;
1610         uint32_t klen;
1611         switch (tlv->type) {
1612          #if 0 /*(not implemented here)*/
1613           case PP2_TYPE_ALPN:
1614           case PP2_TYPE_AUTHORITY:
1615           case PP2_TYPE_CRC32C:
1616          #endif
1617           case PP2_TYPE_SSL: {
1618             if (len < 5) continue;
1619             static const uint32_t zero = 0;
1620             struct pp2_tlv_ssl *tlv_ssl =
1621               (struct pp2_tlv_ssl *)(void *)((char *)tlv+3);
1622             struct pp2_tlv *subtlv = tlv;
1623             if (tlv_ssl->client & PP2_CLIENT_SSL) {
1624                 con->proto_default_port = 443; /* "https" */
1625             }
1626             if ((tlv_ssl->client & (PP2_CLIENT_CERT_CONN|PP2_CLIENT_CERT_SESS))
1627                 && 0 == memcmp(&tlv_ssl->verify, &zero, 4)) { /* misaligned */
1628                 hctx->ssl_client_verify = 1;
1629             }
1630             if (len < 5 + 3) continue;
1631             if (NULL == hctx->env) hctx->env = array_init(8);
1632             for (uint32_t subsz = len-5, n = 5; subsz >= 3; subsz -= 3 + n) {
1633                 subtlv = (struct pp2_tlv *)((char *)subtlv + 3 + n);
1634                 n = ((uint32_t)subtlv->length_hi << 8) | subtlv->length_lo;
1635                 if (3 + n > subsz) break; /*(invalid TLV)*/
1636                 switch (subtlv->type) {
1637                   case PP2_SUBTYPE_SSL_VERSION:
1638                     k = "SSL_PROTOCOL";
1639                     klen = sizeof("SSL_PROTOCOL")-1;
1640                     break;
1641                   case PP2_SUBTYPE_SSL_CN:
1642                     /* (tlv_ssl->client & PP2_CLIENT_CERT_CONN)
1643                      *   or
1644                      * (tlv_ssl->client & PP2_CLIENT_CERT_SESS) */
1645                     k = "SSL_CLIENT_S_DN_CN";
1646                     klen = sizeof("SSL_CLIENT_S_DN_CN")-1;
1647                     break;
1648                   case PP2_SUBTYPE_SSL_CIPHER:
1649                     k = "SSL_CIPHER";
1650                     klen = sizeof("SSL_CIPHER")-1;
1651                     break;
1652                   case PP2_SUBTYPE_SSL_SIG_ALG:
1653                     k = "SSL_SERVER_A_SIG";
1654                     klen = sizeof("SSL_SERVER_A_SIG")-1;
1655                     break;
1656                   case PP2_SUBTYPE_SSL_KEY_ALG:
1657                     k = "SSL_SERVER_A_KEY";
1658                     klen = sizeof("SSL_SERVER_A_KEY")-1;
1659                     break;
1660                   default:
1661                     continue;
1662                 }
1663                 array_set_key_value(hctx->env, k, klen, (char *)subtlv+3, n);
1664             }
1665             continue;
1666           }
1667           case PP2_TYPE_UNIQUE_ID:
1668             k = "PP2_UNIQUE_ID";
1669             klen = sizeof("PP2_UNIQUE_ID")-1;
1670             break;
1671          #if 0 /*(not implemented here)*/
1672           case PP2_TYPE_NETNS:
1673          #endif
1674           /*case PP2_TYPE_NOOP:*//* no-op */
1675           default:
1676             continue;
1677         }
1678         if (NULL == hctx->env) hctx->env = array_init(8);
1679         array_set_key_value(hctx->env, k, klen, (char *)tlv+3, len);
1680     }
1681 
1682     return 0;
1683 }
1684 
1685 
1686 static int mod_extforward_network_read (connection *con,
1687                                         chunkqueue *cq, off_t max_bytes)
1688 {
1689     /* XXX: when using hap-PROXY protocol, currently avoid overhead of setting
1690      * _L_ environment variables for mod_proxy to accurately set Forwarded hdr
1691      * In the future, might add config switch to enable doing this extra work */
1692 
1693     union hap_PROXY_hdr hdr;
1694     log_error_st *errh;
1695     const int family = sock_addr_get_family(&con->dst_addr);
1696     int rc = hap_PROXY_recv(con->fd, &hdr, family, SOCK_STREAM);
1697     switch (rc) {
1698       case  2: rc = mod_extforward_hap_PROXY_v2(con, &hdr); break;
1699       case  1: rc = mod_extforward_hap_PROXY_v1(con, &hdr); break;
1700       case  0: return  0; /*(errno == EAGAIN || errno == EWOULDBLOCK)*/
1701       case -1: errh = con->srv->errh;
1702                log_perror(errh,__FILE__,__LINE__,"hap-PROXY recv()");
1703                rc = -1; break;
1704       case -2: errh = con->srv->errh;
1705                log_error(errh,__FILE__,__LINE__,
1706                  "hap-PROXY proto received invalid/unsupported request");
1707                __attribute_fallthrough__
1708       default: rc = -1; break;
1709     }
1710 
1711     handler_ctx *hctx =
1712       con->plugin_ctx[mod_extforward_plugin_data_singleton->id];
1713     con->network_read = hctx->saved_network_read;
1714     hctx->saved_network_read = NULL;
1715     return (0 == rc) ? con->network_read(con, cq, max_bytes) : rc;
1716 }
1717