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