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