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