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