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