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