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