1 #include "first.h" 2 3 #include <string.h> 4 #include <stdlib.h> 5 6 #include "gw_backend.h" 7 #include "base.h" 8 #include "array.h" 9 #include "buffer.h" 10 #include "http_kv.h" 11 #include "http_header.h" 12 #include "log.h" 13 #include "sock_addr.h" 14 #include "status_counter.h" 15 16 /** 17 * 18 * HTTP reverse proxy 19 * 20 * TODO: - HTTP/1.1 21 * - HTTP/1.1 persistent connection with upstream servers 22 */ 23 24 /* (future: might split struct and move part to http-header-glue.c) */ 25 typedef struct http_header_remap_opts { 26 const array *urlpaths; 27 const array *hosts_request; 28 const array *hosts_response; 29 int https_remap; 30 int upgrade; 31 int connect_method; 32 /*(not used in plugin_config, but used in handler_ctx)*/ 33 const buffer *http_host; 34 const buffer *forwarded_host; 35 const data_string *forwarded_urlpath; 36 } http_header_remap_opts; 37 38 typedef enum { 39 PROXY_FORWARDED_NONE = 0x00, 40 PROXY_FORWARDED_FOR = 0x01, 41 PROXY_FORWARDED_PROTO = 0x02, 42 PROXY_FORWARDED_HOST = 0x04, 43 PROXY_FORWARDED_BY = 0x08, 44 PROXY_FORWARDED_REMOTE_USER = 0x10 45 } proxy_forwarded_t; 46 47 typedef struct { 48 gw_plugin_config gw; 49 array *forwarded_params; 50 array *header_params; 51 unsigned short replace_http_host; 52 unsigned int forwarded; 53 54 http_header_remap_opts header; 55 } plugin_config; 56 57 typedef struct { 58 PLUGIN_DATA; 59 plugin_config **config_storage; 60 61 plugin_config conf; 62 } plugin_data; 63 64 static int proxy_check_extforward; 65 66 typedef struct { 67 gw_handler_ctx gw; 68 http_response_opts opts; 69 plugin_config conf; 70 } handler_ctx; 71 72 73 INIT_FUNC(mod_proxy_init) { 74 plugin_data *p; 75 76 p = calloc(1, sizeof(*p)); 77 78 return p; 79 } 80 81 82 FREE_FUNC(mod_proxy_free) { 83 plugin_data *p = p_d; 84 85 UNUSED(srv); 86 87 if (p->config_storage) { 88 size_t i; 89 for (i = 0; i < srv->config_context->used; i++) { 90 plugin_config *s = p->config_storage[i]; 91 92 if (NULL == s) continue; 93 94 array_free(s->forwarded_params); 95 array_free(s->header_params); 96 97 /*assert(0 == offsetof(s->gw));*/ 98 gw_plugin_config_free(&s->gw); 99 /*free(s);*//*free'd by gw_plugin_config_free()*/ 100 } 101 free(p->config_storage); 102 } 103 104 free(p); 105 106 return HANDLER_GO_ON; 107 } 108 109 SETDEFAULTS_FUNC(mod_proxy_set_defaults) { 110 plugin_data *p = p_d; 111 data_unset *du; 112 size_t i = 0; 113 114 config_values_t cv[] = { 115 { "proxy.server", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ 116 { "proxy.debug", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 1 */ 117 { "proxy.balance", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 2 */ 118 { "proxy.replace-http-host", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 3 */ 119 { "proxy.forwarded", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 4 */ 120 { "proxy.header", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 5 */ 121 { "proxy.map-extensions", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 6 */ 122 { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } 123 }; 124 125 p->config_storage = calloc(srv->config_context->used, sizeof(plugin_config *)); 126 127 for (i = 0; i < srv->config_context->used; i++) { 128 data_config const* config = (data_config const*)srv->config_context->data[i]; 129 plugin_config *s; 130 131 s = calloc(1, sizeof(plugin_config)); 132 s->gw.debug = 0; 133 s->replace_http_host = 0; 134 s->forwarded_params = array_init(); 135 s->forwarded = PROXY_FORWARDED_NONE; 136 s->header_params = array_init(); 137 s->gw.ext_mapping = array_init(); 138 139 cv[0].destination = NULL; /* T_CONFIG_LOCAL */ 140 cv[1].destination = &(s->gw.debug); 141 cv[2].destination = NULL; /* T_CONFIG_LOCAL */ 142 cv[3].destination = &(s->replace_http_host); 143 cv[4].destination = s->forwarded_params; 144 cv[5].destination = s->header_params; 145 cv[6].destination = s->gw.ext_mapping; 146 147 p->config_storage[i] = s; 148 149 if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) { 150 return HANDLER_ERROR; 151 } 152 153 du = array_get_element(config->value, "proxy.server"); 154 if (!gw_set_defaults_backend(srv, (gw_plugin_data *)p, du, i, 0)) { 155 return HANDLER_ERROR; 156 } 157 158 du = array_get_element(config->value, "proxy.balance"); 159 if (!gw_set_defaults_balance(srv, &s->gw, du)) { 160 return HANDLER_ERROR; 161 } 162 163 /* disable check-local for all exts (default enabled) */ 164 if (s->gw.exts) { /*(check after gw_set_defaults_backend())*/ 165 for (size_t j = 0; j < s->gw.exts->used; ++j) { 166 gw_extension *ex = s->gw.exts->exts[j]; 167 for (size_t n = 0; n < ex->used; ++n) { 168 ex->hosts[n]->check_local = 0; 169 } 170 } 171 } 172 173 if (!array_is_kvany(s->forwarded_params)) { 174 log_error_write(srv, __FILE__, __LINE__, "s", 175 "unexpected value for proxy.forwarded; expected ( \"param\" => \"value\" )"); 176 return HANDLER_ERROR; 177 } 178 for (size_t j = 0, used = s->forwarded_params->used; j < used; ++j) { 179 proxy_forwarded_t param; 180 du = s->forwarded_params->data[j]; 181 if (buffer_is_equal_string(du->key, CONST_STR_LEN("by"))) { 182 param = PROXY_FORWARDED_BY; 183 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("for"))) { 184 param = PROXY_FORWARDED_FOR; 185 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("host"))) { 186 param = PROXY_FORWARDED_HOST; 187 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("proto"))) { 188 param = PROXY_FORWARDED_PROTO; 189 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("remote_user"))) { 190 param = PROXY_FORWARDED_REMOTE_USER; 191 } else { 192 log_error_write(srv, __FILE__, __LINE__, "sb", 193 "proxy.forwarded keys must be one of: by, for, host, proto, remote_user, but not:", du->key); 194 return HANDLER_ERROR; 195 } 196 if (du->type == TYPE_STRING) { 197 data_string *ds = (data_string *)du; 198 if (buffer_is_equal_string(ds->value, CONST_STR_LEN("enable"))) { 199 s->forwarded |= param; 200 } else if (!buffer_is_equal_string(ds->value, CONST_STR_LEN("disable"))) { 201 log_error_write(srv, __FILE__, __LINE__, "sb", 202 "proxy.forwarded values must be one of: 0, 1, enable, disable; error for key:", du->key); 203 return HANDLER_ERROR; 204 } 205 } else if (du->type == TYPE_INTEGER) { 206 data_integer *di = (data_integer *)du; 207 if (di->value) s->forwarded |= param; 208 } else { 209 log_error_write(srv, __FILE__, __LINE__, "sb", 210 "proxy.forwarded values must be one of: 0, 1, enable, disable; error for key:", du->key); 211 return HANDLER_ERROR; 212 } 213 } 214 215 if (!array_is_kvany(s->header_params)) { 216 log_error_write(srv, __FILE__, __LINE__, "s", 217 "unexpected value for proxy.header; expected ( \"param\" => ( \"key\" => \"value\" ) )"); 218 return HANDLER_ERROR; 219 } 220 for (size_t j = 0, used = s->header_params->used; j < used; ++j) { 221 data_array *da = (data_array *)s->header_params->data[j]; 222 if (buffer_is_equal_string(da->key, CONST_STR_LEN("https-remap"))) { 223 data_string *ds = (data_string *)da; 224 if (ds->type != TYPE_STRING) { 225 log_error_write(srv, __FILE__, __LINE__, "s", 226 "unexpected value for proxy.header; expected \"enable\" or \"disable\" for https-remap"); 227 return HANDLER_ERROR; 228 } 229 s->header.https_remap = !buffer_is_equal_string(ds->value, CONST_STR_LEN("disable")) 230 && !buffer_is_equal_string(ds->value, CONST_STR_LEN("0")); 231 continue; 232 } 233 else if (buffer_is_equal_string(da->key, CONST_STR_LEN("upgrade"))) { 234 data_string *ds = (data_string *)da; 235 if (ds->type != TYPE_STRING) { 236 log_error_write(srv, __FILE__, __LINE__, "s", 237 "unexpected value for proxy.header; expected \"upgrade\" => \"enable\" or \"disable\""); 238 return HANDLER_ERROR; 239 } 240 s->header.upgrade = !buffer_is_equal_string(ds->value, CONST_STR_LEN("disable")) 241 && !buffer_is_equal_string(ds->value, CONST_STR_LEN("0")); 242 continue; 243 } 244 else if (buffer_is_equal_string(da->key, CONST_STR_LEN("connect"))) { 245 data_string *ds = (data_string *)da; 246 if (ds->type != TYPE_STRING) { 247 log_error_write(srv, __FILE__, __LINE__, "s", 248 "unexpected value for proxy.header; expected \"connect\" => \"enable\" or \"disable\""); 249 return HANDLER_ERROR; 250 } 251 s->header.connect_method = !buffer_is_equal_string(ds->value, CONST_STR_LEN("disable")) 252 && !buffer_is_equal_string(ds->value, CONST_STR_LEN("0")); 253 continue; 254 } 255 if (da->type != TYPE_ARRAY || !array_is_kvstring(da->value)) { 256 log_error_write(srv, __FILE__, __LINE__, "sb", 257 "unexpected value for proxy.header; expected ( \"param\" => ( \"key\" => \"value\" ) ) near key", da->key); 258 return HANDLER_ERROR; 259 } 260 if (buffer_is_equal_string(da->key, CONST_STR_LEN("map-urlpath"))) { 261 s->header.urlpaths = da->value; 262 } 263 else if (buffer_is_equal_string(da->key, CONST_STR_LEN("map-host-request"))) { 264 s->header.hosts_request = da->value; 265 } 266 else if (buffer_is_equal_string(da->key, CONST_STR_LEN("map-host-response"))) { 267 s->header.hosts_response = da->value; 268 } 269 else { 270 log_error_write(srv, __FILE__, __LINE__, "sb", 271 "unexpected key for proxy.header; expected ( \"param\" => ( \"key\" => \"value\" ) ) near key", da->key); 272 return HANDLER_ERROR; 273 } 274 } 275 } 276 277 for (i = 0; i < srv->srvconf.modules->used; i++) { 278 data_string *ds = (data_string *)srv->srvconf.modules->data[i]; 279 if (buffer_is_equal_string(ds->value, CONST_STR_LEN("mod_extforward"))) { 280 proxy_check_extforward = 1; 281 break; 282 } 283 } 284 285 return HANDLER_GO_ON; 286 } 287 288 289 /* (future: might move to http-header-glue.c) */ 290 static const buffer * http_header_remap_host_match (buffer *b, size_t off, http_header_remap_opts *remap_hdrs, int is_req, size_t alen) 291 { 292 const array *hosts = is_req 293 ? remap_hdrs->hosts_request 294 : remap_hdrs->hosts_response; 295 if (hosts) { 296 const char * const s = b->ptr+off; 297 for (size_t i = 0, used = hosts->used; i < used; ++i) { 298 const data_string * const ds = (data_string *)hosts->data[i]; 299 const buffer *k = ds->key; 300 size_t mlen = buffer_string_length(k); 301 if (1 == mlen && k->ptr[0] == '-') { 302 /* match with authority provided in Host (if is_req) 303 * (If no Host in client request, then matching against empty 304 * string will probably not match, and no remap will be 305 * performed) */ 306 k = is_req 307 ? remap_hdrs->http_host 308 : remap_hdrs->forwarded_host; 309 if (NULL == k) continue; 310 mlen = buffer_string_length(k); 311 } 312 if (buffer_eq_icase_ss(s, alen, k->ptr, mlen)) { 313 if (buffer_is_equal_string(ds->value, CONST_STR_LEN("-"))) { 314 return remap_hdrs->http_host; 315 } 316 else if (!buffer_string_is_empty(ds->value)) { 317 /*(save first matched request host for response match)*/ 318 if (is_req && NULL == remap_hdrs->forwarded_host) 319 remap_hdrs->forwarded_host = ds->value; 320 return ds->value; 321 } /*(else leave authority as-is and stop matching)*/ 322 break; 323 } 324 } 325 } 326 return NULL; 327 } 328 329 330 /* (future: might move to http-header-glue.c) */ 331 static size_t http_header_remap_host (buffer *b, size_t off, http_header_remap_opts *remap_hdrs, int is_req, size_t alen) 332 { 333 const buffer * const m = 334 http_header_remap_host_match(b, off, remap_hdrs, is_req, alen); 335 if (NULL == m) return alen; /*(no match; return original authority length)*/ 336 337 buffer_substr_replace(b, off, alen, m); 338 return buffer_string_length(m); /*(length of replacement authority)*/ 339 } 340 341 342 /* (future: might move to http-header-glue.c) */ 343 static size_t http_header_remap_urlpath (buffer *b, size_t off, http_header_remap_opts *remap_hdrs, int is_req) 344 { 345 const array *urlpaths = remap_hdrs->urlpaths; 346 if (urlpaths) { 347 const char * const s = b->ptr+off; 348 const size_t plen = buffer_string_length(b) - off; /*(urlpath len)*/ 349 if (is_req) { /* request */ 350 for (size_t i = 0, used = urlpaths->used; i < used; ++i) { 351 const data_string * const ds = (data_string *)urlpaths->data[i]; 352 const size_t mlen = buffer_string_length(ds->key); 353 if (mlen <= plen && 0 == memcmp(s, ds->key->ptr, mlen)) { 354 if (NULL == remap_hdrs->forwarded_urlpath) 355 remap_hdrs->forwarded_urlpath = ds; 356 buffer_substr_replace(b, off, mlen, ds->value); 357 return buffer_string_length(ds->value);/*(replacement len)*/ 358 } 359 } 360 } 361 else { /* response; perform reverse map */ 362 if (NULL != remap_hdrs->forwarded_urlpath) { 363 const data_string * const ds = remap_hdrs->forwarded_urlpath; 364 const size_t mlen = buffer_string_length(ds->value); 365 if (mlen <= plen && 0 == memcmp(s, ds->value->ptr, mlen)) { 366 buffer_substr_replace(b, off, mlen, ds->key); 367 return buffer_string_length(ds->key); /*(replacement len)*/ 368 } 369 } 370 for (size_t i = 0, used = urlpaths->used; i < used; ++i) { 371 const data_string * const ds = (data_string *)urlpaths->data[i]; 372 const size_t mlen = buffer_string_length(ds->value); 373 if (mlen <= plen && 0 == memcmp(s, ds->value->ptr, mlen)) { 374 buffer_substr_replace(b, off, mlen, ds->key); 375 return buffer_string_length(ds->key); /*(replacement len)*/ 376 } 377 } 378 } 379 } 380 return 0; 381 } 382 383 384 /* (future: might move to http-header-glue.c) */ 385 static void http_header_remap_uri (buffer *b, size_t off, http_header_remap_opts *remap_hdrs, int is_req) 386 { 387 /* find beginning of URL-path (might be preceded by scheme://authority 388 * (caller should make sure any leading whitespace is prior to offset) */ 389 if (b->ptr[off] != '/') { 390 char *s = b->ptr+off; 391 size_t alen; /*(authority len (host len))*/ 392 size_t slen; /*(scheme len)*/ 393 const buffer *m; 394 /* skip over scheme and authority of URI to find beginning of URL-path 395 * (value might conceivably be relative URL-path instead of URI) */ 396 if (NULL == (s = strchr(s, ':')) || s[1] != '/' || s[2] != '/') return; 397 slen = s - (b->ptr+off); 398 s += 3; 399 off = (size_t)(s - b->ptr); 400 if (NULL != (s = strchr(s, '/'))) { 401 alen = (size_t)(s - b->ptr) - off; 402 if (0 == alen) return; /*(empty authority, e.g. "http:///")*/ 403 } 404 else { 405 alen = buffer_string_length(b) - off; 406 if (0 == alen) return; /*(empty authority, e.g. "http:///")*/ 407 buffer_append_string_len(b, CONST_STR_LEN("/")); 408 } 409 410 /* remap authority (if configured) and set offset to url-path */ 411 m = http_header_remap_host_match(b, off, remap_hdrs, is_req, alen); 412 if (NULL != m) { 413 if (remap_hdrs->https_remap 414 && (is_req ? 5==slen && 0==memcmp(b->ptr+off-slen-3,"https",5) 415 : 4==slen && 0==memcmp(b->ptr+off-slen-3,"http",4))){ 416 if (is_req) { 417 memcpy(b->ptr+off-slen-3+4,"://",3); /*("https"=>"http")*/ 418 --off; 419 ++alen; 420 } 421 else {/*(!is_req)*/ 422 memcpy(b->ptr+off-slen-3+4,"s://",4); /*("http" =>"https")*/ 423 ++off; 424 --alen; 425 } 426 } 427 buffer_substr_replace(b, off, alen, m); 428 alen = buffer_string_length(m);/*(length of replacement authority)*/ 429 } 430 off += alen; 431 } 432 433 /* remap URLs (if configured) */ 434 http_header_remap_urlpath(b, off, remap_hdrs, is_req); 435 } 436 437 438 /* (future: might move to http-header-glue.c) */ 439 static void http_header_remap_setcookie (buffer *b, size_t off, http_header_remap_opts *remap_hdrs) 440 { 441 /* Given the special-case of Set-Cookie and the (too) loosely restricted 442 * characters allowed, for best results, the Set-Cookie value should be the 443 * entire string in b from offset to end of string. In response headers, 444 * lighttpd may concatenate multiple Set-Cookie headers into single entry 445 * in con->response.headers, separated by "\r\nSet-Cookie: " */ 446 for (char *s = b->ptr+off, *e; *s; s = e) { 447 size_t len; 448 { 449 while (*s != ';' && *s != '\n' && *s != '\0') ++s; 450 if (*s == '\n') { 451 /*(include +1 for '\n', but leave ' ' for ++s below)*/ 452 s += sizeof("Set-Cookie:"); 453 } 454 if ('\0' == *s) return; 455 do { ++s; } while (*s == ' ' || *s == '\t'); 456 if ('\0' == *s) return; 457 e = s+1; 458 if ('=' == *s) continue; 459 /*(interested only in Domain and Path attributes)*/ 460 while (*e != '=' && *e != '\0') ++e; 461 if ('\0' == *e) return; 462 ++e; 463 switch ((int)(e - s - 1)) { 464 case 4: 465 if (buffer_eq_icase_ssn(s, "path", 4)) { 466 if (*e == '"') ++e; 467 if (*e != '/') continue; 468 off = (size_t)(e - b->ptr); 469 len = http_header_remap_urlpath(b, off, remap_hdrs, 0); 470 e = b->ptr+off+len; /*(b may have been reallocated)*/ 471 continue; 472 } 473 break; 474 case 6: 475 if (buffer_eq_icase_ssn(s, "domain", 6)) { 476 size_t alen = 0; 477 if (*e == '"') ++e; 478 if (*e == '.') ++e; 479 if (*e == ';') continue; 480 off = (size_t)(e - b->ptr); 481 for (char c; (c = e[alen]) != ';' && c != ' ' && c != '\t' 482 && c != '\r' && c != '\0'; ++alen); 483 len = http_header_remap_host(b, off, remap_hdrs, 0, alen); 484 e = b->ptr+off+len; /*(b may have been reallocated)*/ 485 continue; 486 } 487 break; 488 default: 489 break; 490 } 491 } 492 } 493 } 494 495 496 static void buffer_append_string_backslash_escaped(buffer *b, const char *s, size_t len) { 497 /* (future: might move to buffer.c) */ 498 size_t j = 0; 499 char *p; 500 501 buffer_string_prepare_append(b, len*2 + 4); 502 p = b->ptr + buffer_string_length(b); 503 504 for (size_t i = 0; i < len; ++i) { 505 int c = s[i]; 506 if (c == '"' || c == '\\' || c == 0x7F || (c < 0x20 && c != '\t')) 507 p[j++] = '\\'; 508 p[j++] = c; 509 } 510 511 buffer_commit(b, j); 512 } 513 514 static void proxy_set_Forwarded(connection *con, const unsigned int flags) { 515 buffer *b = NULL, *efor = NULL, *eproto = NULL, *ehost = NULL; 516 int semicolon = 0; 517 518 if (proxy_check_extforward) { 519 efor = 520 http_header_env_get(con, CONST_STR_LEN("_L_EXTFORWARD_ACTUAL_FOR")); 521 eproto = 522 http_header_env_get(con, CONST_STR_LEN("_L_EXTFORWARD_ACTUAL_PROTO")); 523 ehost = 524 http_header_env_get(con, CONST_STR_LEN("_L_EXTFORWARD_ACTUAL_HOST")); 525 } 526 527 /* note: set "Forwarded" prior to updating X-Forwarded-For (below) */ 528 529 if (flags) 530 b = http_header_request_get(con, HTTP_HEADER_FORWARDED, CONST_STR_LEN("Forwarded")); 531 532 if (flags && NULL == b) { 533 buffer *xff = 534 http_header_request_get(con, HTTP_HEADER_X_FORWARDED_FOR, CONST_STR_LEN("X-Forwarded-For")); 535 http_header_request_set(con, HTTP_HEADER_FORWARDED, 536 CONST_STR_LEN("Forwarded"), 537 CONST_STR_LEN("x")); /*(must not be blank for _get below)*/ 538 #ifdef __COVERITY__ 539 force_assert(NULL != b); /*(not NULL because created directly above)*/ 540 #endif 541 b = http_header_request_get(con, HTTP_HEADER_FORWARDED, CONST_STR_LEN("Forwarded")); 542 buffer_clear(b); 543 if (NULL != xff) { 544 /* use X-Forwarded-For contents to seed Forwarded */ 545 char *s = xff->ptr; 546 size_t used = buffer_string_length(xff); 547 for (size_t i=0, j, ipv6; i < used; ++i) { 548 while (s[i] == ' ' || s[i] == '\t' || s[i] == ',') ++i; 549 if (s[i] == '\0') break; 550 j = i; 551 do { 552 ++i; 553 } while (s[i]!=' ' && s[i]!='\t' && s[i]!=',' && s[i]!='\0'); 554 buffer_append_string_len(b, CONST_STR_LEN("for=")); 555 /* over-simplified test expecting only IPv4 or IPv6 addresses, 556 * (not expecting :port, so treat existence of colon as IPv6, 557 * and not expecting unix paths, especially not containing ':') 558 * quote all strings, backslash-escape since IPs not validated*/ 559 ipv6 = (NULL != memchr(s+j, ':', i-j)); /*(over-simplified) */ 560 buffer_append_string_len(b, CONST_STR_LEN("\"")); 561 if (ipv6) 562 buffer_append_string_len(b, CONST_STR_LEN("[")); 563 buffer_append_string_backslash_escaped(b, s+j, i-j); 564 if (ipv6) 565 buffer_append_string_len(b, CONST_STR_LEN("]")); 566 buffer_append_string_len(b, CONST_STR_LEN("\"")); 567 buffer_append_string_len(b, CONST_STR_LEN(", ")); 568 } 569 } 570 } else if (flags) { /*(NULL != b)*/ 571 buffer_append_string_len(b, CONST_STR_LEN(", ")); 572 } 573 574 if (flags & PROXY_FORWARDED_FOR) { 575 int family = sock_addr_get_family(&con->dst_addr); 576 buffer_append_string_len(b, CONST_STR_LEN("for=")); 577 if (NULL != efor) { 578 /* over-simplified test expecting only IPv4 or IPv6 addresses, 579 * (not expecting :port, so treat existence of colon as IPv6, 580 * and not expecting unix paths, especially not containing ':') 581 * quote all strings and backslash-escape since IPs not validated 582 * (should be IP from original con->dst_addr_buf, 583 * so trustable and without :port) */ 584 int ipv6 = (NULL != strchr(efor->ptr, ':')); 585 buffer_append_string_len(b, CONST_STR_LEN("\"")); 586 if (ipv6) buffer_append_string_len(b, CONST_STR_LEN("[")); 587 buffer_append_string_backslash_escaped( 588 b, CONST_BUF_LEN(efor)); 589 if (ipv6) buffer_append_string_len(b, CONST_STR_LEN("]")); 590 buffer_append_string_len(b, CONST_STR_LEN("\"")); 591 } else if (family == AF_INET) { 592 /*(Note: if :port is added, then must be quoted-string: 593 * e.g. for="...:port")*/ 594 buffer_append_string_buffer(b, con->dst_addr_buf); 595 } else if (family == AF_INET6) { 596 buffer_append_string_len(b, CONST_STR_LEN("\"[")); 597 buffer_append_string_buffer(b, con->dst_addr_buf); 598 buffer_append_string_len(b, CONST_STR_LEN("]\"")); 599 } else { 600 buffer_append_string_len(b, CONST_STR_LEN("\"")); 601 buffer_append_string_backslash_escaped( 602 b, CONST_BUF_LEN(con->dst_addr_buf)); 603 buffer_append_string_len(b, CONST_STR_LEN("\"")); 604 } 605 semicolon = 1; 606 } 607 608 if (flags & PROXY_FORWARDED_BY) { 609 int family = sock_addr_get_family(&con->srv_socket->addr); 610 /* Note: getsockname() and inet_ntop() are expensive operations. 611 * (recommendation: do not to enable by=... unless required) 612 * future: might use con->srv_socket->srv_token if addr is not 613 * INADDR_ANY or in6addr_any, but must omit optional :port 614 * from con->srv_socket->srv_token for consistency */ 615 616 if (semicolon) buffer_append_string_len(b, CONST_STR_LEN(";")); 617 buffer_append_string_len(b, CONST_STR_LEN("by=")); 618 buffer_append_string_len(b, CONST_STR_LEN("\"")); 619 #ifdef HAVE_SYS_UN_H 620 /* special-case: might need to encode unix domain socket path */ 621 if (family == AF_UNIX) { 622 buffer_append_string_backslash_escaped( 623 b, CONST_BUF_LEN(con->srv_socket->srv_token)); 624 } 625 else 626 #endif 627 { 628 sock_addr addr; 629 socklen_t addrlen = sizeof(addr); 630 if (0 == getsockname(con->fd,(struct sockaddr *)&addr, &addrlen)) { 631 sock_addr_stringify_append_buffer(b, &addr); 632 } 633 } 634 buffer_append_string_len(b, CONST_STR_LEN("\"")); 635 semicolon = 1; 636 } 637 638 if (flags & PROXY_FORWARDED_PROTO) { 639 /* expecting "http" or "https" 640 * (not checking if quoted-string and encoding needed) */ 641 if (semicolon) buffer_append_string_len(b, CONST_STR_LEN(";")); 642 buffer_append_string_len(b, CONST_STR_LEN("proto=")); 643 if (NULL != eproto) { 644 buffer_append_string_buffer(b, eproto); 645 } else if (con->srv_socket->is_ssl) { 646 buffer_append_string_len(b, CONST_STR_LEN("https")); 647 } else { 648 buffer_append_string_len(b, CONST_STR_LEN("http")); 649 } 650 semicolon = 1; 651 } 652 653 if (flags & PROXY_FORWARDED_HOST) { 654 if (NULL != ehost) { 655 if (semicolon) 656 buffer_append_string_len(b, CONST_STR_LEN(";")); 657 buffer_append_string_len(b, CONST_STR_LEN("host=\"")); 658 buffer_append_string_backslash_escaped( 659 b, CONST_BUF_LEN(ehost)); 660 buffer_append_string_len(b, CONST_STR_LEN("\"")); 661 semicolon = 1; 662 } else if (!buffer_string_is_empty(con->request.http_host)) { 663 if (semicolon) 664 buffer_append_string_len(b, CONST_STR_LEN(";")); 665 buffer_append_string_len(b, CONST_STR_LEN("host=\"")); 666 buffer_append_string_backslash_escaped( 667 b, CONST_BUF_LEN(con->request.http_host)); 668 buffer_append_string_len(b, CONST_STR_LEN("\"")); 669 semicolon = 1; 670 } 671 } 672 673 if (flags & PROXY_FORWARDED_REMOTE_USER) { 674 buffer *remote_user = 675 http_header_env_get(con, CONST_STR_LEN("REMOTE_USER")); 676 if (NULL != remote_user) { 677 if (semicolon) 678 buffer_append_string_len(b, CONST_STR_LEN(";")); 679 buffer_append_string_len(b, CONST_STR_LEN("remote_user=\"")); 680 buffer_append_string_backslash_escaped( 681 b, CONST_BUF_LEN(remote_user)); 682 buffer_append_string_len(b, CONST_STR_LEN("\"")); 683 semicolon = 1; 684 } 685 } 686 687 /* legacy X-* headers, including X-Forwarded-For */ 688 689 b = (NULL != efor) ? efor : con->dst_addr_buf; 690 http_header_request_set(con, HTTP_HEADER_X_FORWARDED_FOR, 691 CONST_STR_LEN("X-Forwarded-For"), 692 CONST_BUF_LEN(b)); 693 694 b = (NULL != ehost) ? ehost : con->request.http_host; 695 if (!buffer_string_is_empty(b)) { 696 http_header_request_set(con, HTTP_HEADER_OTHER, 697 CONST_STR_LEN("X-Host"), 698 CONST_BUF_LEN(b)); 699 http_header_request_set(con, HTTP_HEADER_OTHER, 700 CONST_STR_LEN("X-Forwarded-Host"), 701 CONST_BUF_LEN(b)); 702 } 703 704 b = (NULL != eproto) ? eproto : con->uri.scheme; 705 http_header_request_set(con, HTTP_HEADER_X_FORWARDED_PROTO, 706 CONST_STR_LEN("X-Forwarded-Proto"), 707 CONST_BUF_LEN(b)); 708 } 709 710 711 static handler_t proxy_create_env(server *srv, gw_handler_ctx *gwhctx) { 712 handler_ctx *hctx = (handler_ctx *)gwhctx; 713 connection *con = hctx->gw.remote_conn; 714 const int remap_headers = (NULL != hctx->conf.header.urlpaths 715 || NULL != hctx->conf.header.hosts_request); 716 const int upgrade = hctx->conf.header.upgrade 717 && (NULL != http_header_request_get(con, HTTP_HEADER_UPGRADE, CONST_STR_LEN("Upgrade"))); 718 size_t rsz = (size_t)(con->read_queue->bytes_out - hctx->gw.wb->bytes_in); 719 buffer * const b = chunkqueue_prepend_buffer_open_sz(hctx->gw.wb, rsz < 65536 ? rsz : con->header_len); 720 721 /* build header */ 722 723 /* request line */ 724 http_method_append(b, con->request.http_method); 725 buffer_append_string_len(b, CONST_STR_LEN(" ")); 726 buffer_append_string_buffer(b, con->request.uri); 727 if (remap_headers) 728 http_header_remap_uri(b, buffer_string_length(b) - buffer_string_length(con->request.uri), &hctx->conf.header, 1); 729 if (!upgrade) 730 buffer_append_string_len(b, CONST_STR_LEN(" HTTP/1.0\r\n")); 731 else 732 buffer_append_string_len(b, CONST_STR_LEN(" HTTP/1.1\r\n")); 733 734 if (hctx->conf.replace_http_host && !buffer_string_is_empty(hctx->gw.host->id)) { 735 if (hctx->gw.conf.debug > 1) { 736 log_error_write(srv, __FILE__, __LINE__, "SBS", 737 "proxy - using \"", hctx->gw.host->id, "\" as HTTP Host"); 738 } 739 buffer_append_string_len(b, CONST_STR_LEN("Host: ")); 740 buffer_append_string_buffer(b, hctx->gw.host->id); 741 buffer_append_string_len(b, CONST_STR_LEN("\r\n")); 742 } else if (!buffer_string_is_empty(con->request.http_host)) { 743 buffer_append_string_len(b, CONST_STR_LEN("Host: ")); 744 buffer_append_string_buffer(b, con->request.http_host); 745 if (remap_headers) { 746 size_t alen = buffer_string_length(con->request.http_host); 747 http_header_remap_host(b, buffer_string_length(b) - alen, &hctx->conf.header, 1, alen); 748 } 749 buffer_append_string_len(b, CONST_STR_LEN("\r\n")); 750 } 751 752 /* "Forwarded" and legacy X- headers */ 753 proxy_set_Forwarded(con, hctx->conf.forwarded); 754 755 if (con->request.content_length > 0 756 || (0 == con->request.content_length 757 && HTTP_METHOD_GET != con->request.http_method 758 && HTTP_METHOD_HEAD != con->request.http_method)) { 759 /* set Content-Length if client sent Transfer-Encoding: chunked 760 * and not streaming to backend (request body has been fully received) */ 761 buffer *vb = http_header_request_get(con, HTTP_HEADER_CONTENT_LENGTH, CONST_STR_LEN("Content-Length")); 762 if (NULL == vb) { 763 char buf[LI_ITOSTRING_LENGTH]; 764 li_itostrn(buf, sizeof(buf), con->request.content_length); 765 http_header_request_set(con, HTTP_HEADER_CONTENT_LENGTH, CONST_STR_LEN("Content-Length"), buf, strlen(buf)); 766 } 767 } 768 769 /* request header */ 770 for (size_t i = 0, used = con->request.headers->used; i < used; ++i) { 771 data_string *ds = (data_string *)con->request.headers->data[i]; 772 const size_t klen = buffer_string_length(ds->key); 773 size_t vlen; 774 switch (klen) { 775 default: 776 break; 777 case 4: 778 if (buffer_is_equal_caseless_string(ds->key, CONST_STR_LEN("Host"))) continue; /*(handled further above)*/ 779 break; 780 case 10: 781 if (buffer_is_equal_caseless_string(ds->key, CONST_STR_LEN("Connection"))) continue; 782 if (buffer_is_equal_caseless_string(ds->key, CONST_STR_LEN("Set-Cookie"))) continue; /*(response header only; avoid accidental reflection)*/ 783 break; 784 case 16: 785 if (buffer_is_equal_caseless_string(ds->key, CONST_STR_LEN("Proxy-Connection"))) continue; 786 break; 787 case 5: 788 /* Do not emit HTTP_PROXY in environment. 789 * Some executables use HTTP_PROXY to configure 790 * outgoing proxy. See also https://httpoxy.org/ */ 791 if (buffer_is_equal_caseless_string(ds->key, CONST_STR_LEN("Proxy"))) continue; 792 break; 793 case 0: 794 continue; 795 } 796 797 vlen = buffer_string_length(ds->value); 798 if (0 == vlen) continue; 799 800 if (buffer_string_space(b) < klen + vlen + 4) { 801 size_t extend = b->size * 2 - buffer_string_length(b); 802 extend = extend > klen + vlen + 4 ? extend : klen + vlen + 4 + 4095; 803 buffer_string_prepare_append(b, extend); 804 } 805 806 buffer_append_string_len(b, ds->key->ptr, klen); 807 buffer_append_string_len(b, CONST_STR_LEN(": ")); 808 buffer_append_string_len(b, ds->value->ptr, vlen); 809 buffer_append_string_len(b, CONST_STR_LEN("\r\n")); 810 811 if (!remap_headers) continue; 812 813 /* check for hdrs for which to remap URIs in-place after append to b */ 814 815 switch (klen) { 816 default: 817 continue; 818 #if 0 /* "URI" is HTTP response header (non-standard; historical in Apache) */ 819 case 3: 820 if (buffer_is_equal_caseless_string(ds->key, CONST_STR_LEN("URI"))) break; 821 continue; 822 #endif 823 #if 0 /* "Location" is HTTP response header */ 824 case 8: 825 if (buffer_is_equal_caseless_string(ds->key, CONST_STR_LEN("Location"))) break; 826 continue; 827 #endif 828 case 11: /* "Destination" is WebDAV request header */ 829 if (buffer_is_equal_caseless_string(ds->key, CONST_STR_LEN("Destination"))) break; 830 continue; 831 case 16: /* "Content-Location" may be HTTP request or response header */ 832 if (buffer_is_equal_caseless_string(ds->key, CONST_STR_LEN("Content-Location"))) break; 833 continue; 834 } 835 836 http_header_remap_uri(b, buffer_string_length(b) - vlen - 2, &hctx->conf.header, 1); 837 } 838 839 if (!upgrade) 840 buffer_append_string_len(b, CONST_STR_LEN("Connection: close\r\n\r\n")); 841 else 842 buffer_append_string_len(b, CONST_STR_LEN("Connection: close, upgrade\r\n\r\n")); 843 844 hctx->gw.wb_reqlen = buffer_string_length(b); 845 chunkqueue_prepend_buffer_commit(hctx->gw.wb); 846 847 if (con->request.content_length) { 848 chunkqueue_append_chunkqueue(hctx->gw.wb, con->request_content_queue); 849 if (con->request.content_length > 0) 850 hctx->gw.wb_reqlen += con->request.content_length; /* total req size */ 851 else /* as-yet-unknown total request size (Transfer-Encoding: chunked)*/ 852 hctx->gw.wb_reqlen = -hctx->gw.wb_reqlen; 853 } 854 855 status_counter_inc(srv, CONST_STR_LEN("proxy.requests")); 856 return HANDLER_GO_ON; 857 } 858 859 860 static handler_t proxy_create_env_connect(server *srv, gw_handler_ctx *gwhctx) { 861 handler_ctx *hctx = (handler_ctx *)gwhctx; 862 connection *con = hctx->gw.remote_conn; 863 con->http_status = 200; /* OK */ 864 con->file_started = 1; 865 gw_set_transparent(srv, &hctx->gw); 866 http_response_upgrade_read_body_unknown(srv, con); 867 868 status_counter_inc(srv, CONST_STR_LEN("proxy.requests")); 869 return HANDLER_GO_ON; 870 } 871 872 873 #define PATCH(x) \ 874 p->conf.x = s->x; 875 #define PATCH_GW(x) \ 876 p->conf.gw.x = s->gw.x; 877 static int mod_proxy_patch_connection(server *srv, connection *con, plugin_data *p) { 878 size_t i, j; 879 plugin_config *s = p->config_storage[0]; 880 881 PATCH_GW(exts); 882 PATCH_GW(exts_auth); 883 PATCH_GW(exts_resp); 884 PATCH_GW(debug); 885 PATCH_GW(ext_mapping); 886 PATCH_GW(balance); 887 PATCH(replace_http_host); 888 PATCH(forwarded); 889 PATCH(header); /*(copies struct)*/ 890 891 /* skip the first, the global context */ 892 for (i = 1; i < srv->config_context->used; i++) { 893 data_config *dc = (data_config *)srv->config_context->data[i]; 894 s = p->config_storage[i]; 895 896 /* condition didn't match */ 897 if (!config_check_cond(srv, con, dc)) continue; 898 899 /* merge config */ 900 for (j = 0; j < dc->value->used; j++) { 901 data_unset *du = dc->value->data[j]; 902 903 if (buffer_is_equal_string(du->key, CONST_STR_LEN("proxy.server"))) { 904 PATCH_GW(exts); 905 PATCH_GW(exts_auth); 906 PATCH_GW(exts_resp); 907 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("proxy.debug"))) { 908 PATCH_GW(debug); 909 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("proxy.balance"))) { 910 PATCH_GW(balance); 911 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("proxy.map-extensions"))) { 912 PATCH_GW(ext_mapping); 913 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("proxy.replace-http-host"))) { 914 PATCH(replace_http_host); 915 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("proxy.forwarded"))) { 916 PATCH(forwarded); 917 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("proxy.header"))) { 918 PATCH(header); /*(copies struct)*/ 919 } 920 } 921 } 922 923 return 0; 924 } 925 #undef PATCH_GW 926 #undef PATCH 927 928 static handler_t proxy_response_headers(server *srv, connection *con, struct http_response_opts_t *opts) { 929 /* response headers just completed */ 930 handler_ctx *hctx = (handler_ctx *)opts->pdata; 931 932 if (con->response.htags & HTTP_HEADER_UPGRADE) { 933 if (hctx->conf.header.upgrade && con->http_status == 101) { 934 /* 101 Switching Protocols; transition to transparent proxy */ 935 gw_set_transparent(srv, &hctx->gw); 936 http_response_upgrade_read_body_unknown(srv, con); 937 } 938 else { 939 con->response.htags &= ~HTTP_HEADER_UPGRADE; 940 #if 0 941 /* preserve prior questionable behavior; likely broken behavior 942 * anyway if backend thinks connection is being upgraded but client 943 * does not receive Connection: upgrade */ 944 http_header_response_unset(con, HTTP_HEADER_UPGRADE, 945 CONST_STR_LEN("Upgrade")) 946 #endif 947 } 948 } 949 950 /* rewrite paths, if needed */ 951 952 if (NULL == hctx->conf.header.urlpaths 953 && NULL == hctx->conf.header.hosts_response) 954 return HANDLER_GO_ON; 955 956 if (con->response.htags & HTTP_HEADER_LOCATION) { 957 buffer *vb = http_header_response_get(con, HTTP_HEADER_LOCATION, CONST_STR_LEN("Location")); 958 if (vb) http_header_remap_uri(vb, 0, &hctx->conf.header, 0); 959 } 960 if (con->response.htags & HTTP_HEADER_CONTENT_LOCATION) { 961 buffer *vb = http_header_response_get(con, HTTP_HEADER_CONTENT_LOCATION, CONST_STR_LEN("Content-Location")); 962 if (vb) http_header_remap_uri(vb, 0, &hctx->conf.header, 0); 963 } 964 if (con->response.htags & HTTP_HEADER_SET_COOKIE) { 965 buffer *vb = http_header_response_get(con, HTTP_HEADER_SET_COOKIE, CONST_STR_LEN("Set-Cookie")); 966 if (vb) http_header_remap_setcookie(vb, 0, &hctx->conf.header); 967 } 968 969 return HANDLER_GO_ON; 970 } 971 972 static handler_t mod_proxy_check_extension(server *srv, connection *con, void *p_d) { 973 plugin_data *p = p_d; 974 handler_t rc; 975 976 if (con->mode != DIRECT) return HANDLER_GO_ON; 977 978 mod_proxy_patch_connection(srv, con, p); 979 if (NULL == p->conf.gw.exts) return HANDLER_GO_ON; 980 981 rc = gw_check_extension(srv, con, (gw_plugin_data *)p, 1, sizeof(handler_ctx)); 982 if (HANDLER_GO_ON != rc) return rc; 983 984 if (con->mode == p->id) { 985 handler_ctx *hctx = con->plugin_ctx[p->id]; 986 hctx->gw.create_env = proxy_create_env; 987 hctx->gw.response = chunk_buffer_acquire(); 988 hctx->gw.opts.backend = BACKEND_PROXY; 989 hctx->gw.opts.pdata = hctx; 990 hctx->gw.opts.headers = proxy_response_headers; 991 992 hctx->conf = p->conf; /*(copies struct)*/ 993 hctx->conf.header.http_host = con->request.http_host; 994 hctx->conf.header.upgrade &= (con->request.http_version == HTTP_VERSION_1_1); 995 /* mod_proxy currently sends all backend requests as http. 996 * https-remap is a flag since it might not be needed if backend 997 * honors Forwarded or X-Forwarded-Proto headers, e.g. by using 998 * lighttpd mod_extforward or similar functionality in backend*/ 999 if (hctx->conf.header.https_remap) { 1000 hctx->conf.header.https_remap = 1001 buffer_is_equal_string(con->uri.scheme, CONST_STR_LEN("https")); 1002 } 1003 1004 if (con->request.http_method == HTTP_METHOD_CONNECT) { 1005 /*(note: not requiring HTTP/1.1 due to too many non-compliant 1006 * clients such as 'openssl s_client')*/ 1007 if (hctx->conf.header.connect_method) { 1008 hctx->gw.create_env = proxy_create_env_connect; 1009 } 1010 else { 1011 con->http_status = 405; /* Method Not Allowed */ 1012 con->mode = DIRECT; 1013 return HANDLER_FINISHED; 1014 } 1015 } 1016 } 1017 1018 return HANDLER_GO_ON; 1019 } 1020 1021 1022 int mod_proxy_plugin_init(plugin *p); 1023 int mod_proxy_plugin_init(plugin *p) { 1024 p->version = LIGHTTPD_VERSION_ID; 1025 p->name = buffer_init_string("proxy"); 1026 1027 p->init = mod_proxy_init; 1028 p->cleanup = mod_proxy_free; 1029 p->set_defaults = mod_proxy_set_defaults; 1030 p->connection_reset = gw_connection_reset; 1031 p->handle_uri_clean = mod_proxy_check_extension; 1032 p->handle_subrequest = gw_handle_subrequest; 1033 p->handle_trigger = gw_handle_trigger; 1034 p->handle_waitpid = gw_handle_waitpid_cb; 1035 1036 p->data = NULL; 1037 1038 return 0; 1039 } 1040