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