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