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 "fdevent.h" 11 #include "http_kv.h" 12 #include "http_header.h" 13 #include "log.h" 14 #include "sock_addr.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 force_http10; 30 int https_remap; 31 int upgrade; 32 int connect_method; 33 /*(not used in plugin_config, but used in handler_ctx)*/ 34 const buffer *http_host; 35 const buffer *forwarded_host; 36 const data_string *forwarded_urlpath; 37 } http_header_remap_opts; 38 39 typedef enum { 40 PROXY_FORWARDED_NONE = 0x00, 41 PROXY_FORWARDED_FOR = 0x01, 42 PROXY_FORWARDED_PROTO = 0x02, 43 PROXY_FORWARDED_HOST = 0x04, 44 PROXY_FORWARDED_BY = 0x08, 45 PROXY_FORWARDED_REMOTE_USER = 0x10 46 } proxy_forwarded_t; 47 48 typedef struct { 49 gw_plugin_config gw; /* start must match layout of gw_plugin_config */ 50 unsigned int replace_http_host; 51 unsigned int forwarded; 52 http_header_remap_opts header; 53 } plugin_config; 54 55 typedef struct { 56 PLUGIN_DATA; 57 pid_t srv_pid; /* must match layout of gw_plugin_data through conf member */ 58 plugin_config conf; 59 plugin_config defaults; 60 } plugin_data; 61 62 static int proxy_check_extforward; 63 64 typedef struct { 65 gw_handler_ctx gw; 66 plugin_config conf; 67 } handler_ctx; 68 69 70 INIT_FUNC(mod_proxy_init) { 71 return calloc(1, sizeof(plugin_data)); 72 } 73 74 75 static void mod_proxy_free_config(plugin_data * const p) 76 { 77 if (NULL == p->cvlist) return; 78 /* (init i to 0 if global context; to 1 to skip empty global context) */ 79 for (int i = !p->cvlist[0].v.u2[1], used = p->nconfig; i < used; ++i) { 80 config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0]; 81 for (; -1 != cpv->k_id; ++cpv) { 82 switch (cpv->k_id) { 83 case 5: /* proxy.header */ 84 if (cpv->vtype == T_CONFIG_LOCAL) free(cpv->v.v); 85 break; 86 default: 87 break; 88 } 89 } 90 } 91 } 92 93 94 FREE_FUNC(mod_proxy_free) { 95 plugin_data * const p = p_d; 96 mod_proxy_free_config(p); 97 gw_free(p); 98 } 99 100 static void mod_proxy_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) 101 { 102 switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */ 103 case 0: /* proxy.server */ 104 if (cpv->vtype == T_CONFIG_LOCAL) { 105 gw_plugin_config * const gw = cpv->v.v; 106 pconf->gw.exts = gw->exts; 107 pconf->gw.exts_auth = gw->exts_auth; 108 pconf->gw.exts_resp = gw->exts_resp; 109 } 110 break; 111 case 1: /* proxy.balance */ 112 /*if (cpv->vtype == T_CONFIG_LOCAL)*//*always true here for this param*/ 113 pconf->gw.balance = (int)cpv->v.u; 114 break; 115 case 2: /* proxy.debug */ 116 pconf->gw.debug = (int)cpv->v.u; 117 break; 118 case 3: /* proxy.map-extensions */ 119 pconf->gw.ext_mapping = cpv->v.a; 120 break; 121 case 4: /* proxy.forwarded */ 122 /*if (cpv->vtype == T_CONFIG_LOCAL)*//*always true here for this param*/ 123 pconf->forwarded = cpv->v.u; 124 break; 125 case 5: /* proxy.header */ 126 /*if (cpv->vtype == T_CONFIG_LOCAL)*//*always true here for this param*/ 127 pconf->header = *(http_header_remap_opts *)cpv->v.v; /*(copies struct)*/ 128 break; 129 case 6: /* proxy.replace-http-host */ 130 pconf->replace_http_host = cpv->v.u; 131 break; 132 default:/* should not happen */ 133 return; 134 } 135 } 136 137 138 static void mod_proxy_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) 139 { 140 do { 141 mod_proxy_merge_config_cpv(pconf, cpv); 142 } while ((++cpv)->k_id != -1); 143 } 144 145 146 static void mod_proxy_patch_config(request_st * const r, plugin_data * const p) 147 { 148 memcpy(&p->conf, &p->defaults, sizeof(plugin_config)); 149 for (int i = 1, used = p->nconfig; i < used; ++i) { 150 if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id)) 151 mod_proxy_merge_config(&p->conf, p->cvlist+p->cvlist[i].v.u2[0]); 152 } 153 } 154 155 156 static unsigned int mod_proxy_parse_forwarded(server *srv, const array *a) 157 { 158 unsigned int forwarded = 0; 159 for (uint32_t j = 0, used = a->used; j < used; ++j) { 160 proxy_forwarded_t param; 161 data_unset *du = a->data[j]; 162 if (buffer_eq_slen(&du->key, CONST_STR_LEN("by"))) 163 param = PROXY_FORWARDED_BY; 164 else if (buffer_eq_slen(&du->key, CONST_STR_LEN("for"))) 165 param = PROXY_FORWARDED_FOR; 166 else if (buffer_eq_slen(&du->key, CONST_STR_LEN("host"))) 167 param = PROXY_FORWARDED_HOST; 168 else if (buffer_eq_slen(&du->key, CONST_STR_LEN("proto"))) 169 param = PROXY_FORWARDED_PROTO; 170 else if (buffer_eq_slen(&du->key, CONST_STR_LEN("remote_user"))) 171 param = PROXY_FORWARDED_REMOTE_USER; 172 else { 173 log_error(srv->errh, __FILE__, __LINE__, 174 "proxy.forwarded keys must be one of: " 175 "by, for, host, proto, remote_user, but not: %s", du->key.ptr); 176 return UINT_MAX; 177 } 178 int val = config_plugin_value_tobool(du, 2); 179 if (2 == val) { 180 log_error(srv->errh, __FILE__, __LINE__, 181 "proxy.forwarded values must be one of: " 182 "0, 1, enable, disable; error for key: %s", du->key.ptr); 183 return UINT_MAX; 184 } 185 if (val) 186 forwarded |= param; 187 } 188 return forwarded; 189 } 190 191 192 static http_header_remap_opts * mod_proxy_parse_header_opts(server *srv, const array *a) 193 { 194 http_header_remap_opts header; 195 memset(&header, 0, sizeof(header)); 196 for (uint32_t j = 0, used = a->used; j < used; ++j) { 197 data_array *da = (data_array *)a->data[j]; 198 if (buffer_eq_slen(&da->key, CONST_STR_LEN("https-remap"))) { 199 int val = config_plugin_value_tobool((data_unset *)da, 2); 200 if (2 == val) { 201 log_error(srv->errh, __FILE__, __LINE__, 202 "unexpected value for proxy.header; " 203 "expected \"https-remap\" => \"enable\" or \"disable\""); 204 return NULL; 205 } 206 header.https_remap = val; 207 continue; 208 } 209 else if (buffer_eq_slen(&da->key, CONST_STR_LEN("force-http10"))) { 210 int val = config_plugin_value_tobool((data_unset *)da, 2); 211 if (2 == val) { 212 log_error(srv->errh, __FILE__, __LINE__, 213 "unexpected value for proxy.header; " 214 "expected \"force-http10\" => \"enable\" or \"disable\""); 215 return NULL; 216 } 217 header.force_http10 = val; 218 continue; 219 } 220 else if (buffer_eq_slen(&da->key, CONST_STR_LEN("upgrade"))) { 221 int val = config_plugin_value_tobool((data_unset *)da, 2); 222 if (2 == val) { 223 log_error(srv->errh, __FILE__, __LINE__, 224 "unexpected value for proxy.header; " 225 "expected \"upgrade\" => \"enable\" or \"disable\""); 226 return NULL; 227 } 228 header.upgrade = val; 229 continue; 230 } 231 else if (buffer_eq_slen(&da->key, CONST_STR_LEN("connect"))) { 232 int val = config_plugin_value_tobool((data_unset *)da, 2); 233 if (2 == val) { 234 log_error(srv->errh, __FILE__, __LINE__, 235 "unexpected value for proxy.header; " 236 "expected \"connect\" => \"enable\" or \"disable\""); 237 return NULL; 238 } 239 header.connect_method = val; 240 continue; 241 } 242 if (da->type != TYPE_ARRAY || !array_is_kvstring(&da->value)) { 243 log_error(srv->errh, __FILE__, __LINE__, 244 "unexpected value for proxy.header; " 245 "expected ( \"param\" => ( \"key\" => \"value\" ) ) near key %s", 246 da->key.ptr); 247 return NULL; 248 } 249 if (buffer_eq_slen(&da->key, CONST_STR_LEN("map-urlpath"))) { 250 header.urlpaths = &da->value; 251 } 252 else if (buffer_eq_slen(&da->key, CONST_STR_LEN("map-host-request"))) { 253 header.hosts_request = &da->value; 254 } 255 else if (buffer_eq_slen(&da->key, CONST_STR_LEN("map-host-response"))) { 256 header.hosts_response = &da->value; 257 } 258 else { 259 log_error(srv->errh, __FILE__, __LINE__, 260 "unexpected key for proxy.header; " 261 "expected ( \"param\" => ( \"key\" => \"value\" ) ) near key %s", 262 da->key.ptr); 263 return NULL; 264 } 265 } 266 267 http_header_remap_opts *opts = malloc(sizeof(header)); 268 force_assert(opts); 269 memcpy(opts, &header, sizeof(header)); 270 return opts; 271 } 272 273 274 SETDEFAULTS_FUNC(mod_proxy_set_defaults) 275 { 276 static const config_plugin_keys_t cpk[] = { 277 { CONST_STR_LEN("proxy.server"), 278 T_CONFIG_ARRAY_KVARRAY, 279 T_CONFIG_SCOPE_CONNECTION } 280 ,{ CONST_STR_LEN("proxy.balance"), 281 T_CONFIG_STRING, 282 T_CONFIG_SCOPE_CONNECTION } 283 ,{ CONST_STR_LEN("proxy.debug"), 284 T_CONFIG_INT, 285 T_CONFIG_SCOPE_CONNECTION } 286 ,{ CONST_STR_LEN("proxy.map-extensions"), 287 T_CONFIG_ARRAY_KVSTRING, 288 T_CONFIG_SCOPE_CONNECTION } 289 ,{ CONST_STR_LEN("proxy.forwarded"), 290 T_CONFIG_ARRAY_KVANY, 291 T_CONFIG_SCOPE_CONNECTION } 292 ,{ CONST_STR_LEN("proxy.header"), 293 T_CONFIG_ARRAY_KVANY, 294 T_CONFIG_SCOPE_CONNECTION } 295 ,{ CONST_STR_LEN("proxy.replace-http-host"), 296 T_CONFIG_BOOL, 297 T_CONFIG_SCOPE_CONNECTION } 298 ,{ NULL, 0, 299 T_CONFIG_UNSET, 300 T_CONFIG_SCOPE_UNSET } 301 }; 302 303 plugin_data * const p = p_d; 304 if (!config_plugin_values_init(srv, p, cpk, "mod_proxy")) 305 return HANDLER_ERROR; 306 307 /* process and validate config directives 308 * (init i to 0 if global context; to 1 to skip empty global context) */ 309 for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) { 310 config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0]; 311 gw_plugin_config *gw = NULL; 312 for (; -1 != cpv->k_id; ++cpv) { 313 switch (cpv->k_id) { 314 case 0: /* proxy.server */ 315 gw = calloc(1, sizeof(gw_plugin_config)); 316 force_assert(gw); 317 if (!gw_set_defaults_backend(srv, (gw_plugin_data *)p, cpv->v.a, 318 gw, 0, cpk[cpv->k_id].k)) { 319 gw_plugin_config_free(gw); 320 return HANDLER_ERROR; 321 } 322 /* error if "mode" = "authorizer"; 323 * proxy can not act as authorizer */ 324 /*(check after gw_set_defaults_backend())*/ 325 if (gw->exts_auth && gw->exts_auth->used) { 326 log_error(srv->errh, __FILE__, __LINE__, 327 "%s must not define any hosts with " 328 "attribute \"mode\" = \"authorizer\"", cpk[cpv->k_id].k); 329 gw_plugin_config_free(gw); 330 return HANDLER_ERROR; 331 } 332 cpv->v.v = gw; 333 cpv->vtype = T_CONFIG_LOCAL; 334 break; 335 case 1: /* proxy.balance */ 336 cpv->v.u = (unsigned int)gw_get_defaults_balance(srv, cpv->v.b); 337 break; 338 case 2: /* proxy.debug */ 339 case 3: /* proxy.map-extensions */ 340 break; 341 case 4: /* proxy.forwarded */ 342 cpv->v.u = mod_proxy_parse_forwarded(srv, cpv->v.a); 343 if (UINT_MAX == cpv->v.u) return HANDLER_ERROR; 344 cpv->vtype = T_CONFIG_LOCAL; 345 break; 346 case 5: /* proxy.header */ 347 cpv->v.v = mod_proxy_parse_header_opts(srv, cpv->v.a); 348 if (NULL == cpv->v.v) return HANDLER_ERROR; 349 cpv->vtype = T_CONFIG_LOCAL; 350 break; 351 case 6: /* proxy.replace-http-host */ 352 break; 353 default:/* should not happen */ 354 break; 355 } 356 } 357 358 /* disable check-local for all exts (default enabled) */ 359 if (gw && gw->exts) { /*(check after gw_set_defaults_backend())*/ 360 gw_exts_clear_check_local(gw->exts); 361 } 362 } 363 364 /* default is 0 */ 365 /*p->defaults.balance = (unsigned int)gw_get_defaults_balance(srv, NULL);*/ 366 367 p->defaults.header.force_http10 = 368 config_feature_bool(srv, "proxy.force-http10", 0); 369 370 /* initialize p->defaults from global config context */ 371 if (p->nconfig > 0 && p->cvlist->v.u2[1]) { 372 const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0]; 373 if (-1 != cpv->k_id) 374 mod_proxy_merge_config(&p->defaults, cpv); 375 } 376 377 /* special-case behavior if mod_extforward is loaded */ 378 for (uint32_t i = 0; i < srv->srvconf.modules->used; ++i) { 379 buffer *m = &((data_string *)srv->srvconf.modules->data[i])->value; 380 if (buffer_eq_slen(m, CONST_STR_LEN("mod_extforward"))) { 381 proxy_check_extforward = 1; 382 break; 383 } 384 } 385 386 return HANDLER_GO_ON; 387 } 388 389 390 /* (future: might move to http-header-glue.c) */ 391 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) 392 { 393 const array *hosts = is_req 394 ? remap_hdrs->hosts_request 395 : remap_hdrs->hosts_response; 396 if (hosts) { 397 const char * const s = b->ptr+off; 398 for (size_t i = 0, used = hosts->used; i < used; ++i) { 399 const data_string * const ds = (data_string *)hosts->data[i]; 400 const buffer *k = &ds->key; 401 size_t mlen = buffer_clen(k); 402 if (1 == mlen && k->ptr[0] == '-') { 403 /* match with authority provided in Host (if is_req) 404 * (If no Host in client request, then matching against empty 405 * string will probably not match, and no remap will be 406 * performed) */ 407 k = is_req 408 ? remap_hdrs->http_host 409 : remap_hdrs->forwarded_host; 410 if (NULL == k) continue; 411 mlen = buffer_clen(k); 412 } 413 if (buffer_eq_icase_ss(s, alen, k->ptr, mlen)) { 414 if (buffer_is_equal_string(&ds->value, CONST_STR_LEN("-"))) { 415 return remap_hdrs->http_host; 416 } 417 else if (!buffer_is_blank(&ds->value)) { 418 /*(save first matched request host for response match)*/ 419 if (is_req && NULL == remap_hdrs->forwarded_host) 420 remap_hdrs->forwarded_host = &ds->value; 421 return &ds->value; 422 } /*(else leave authority as-is and stop matching)*/ 423 break; 424 } 425 } 426 } 427 return NULL; 428 } 429 430 431 /* (future: might move to http-header-glue.c) */ 432 static size_t http_header_remap_host (buffer *b, size_t off, http_header_remap_opts *remap_hdrs, int is_req, size_t alen) 433 { 434 const buffer * const m = 435 http_header_remap_host_match(b, off, remap_hdrs, is_req, alen); 436 if (NULL == m) return alen; /*(no match; return original authority length)*/ 437 438 buffer_substr_replace(b, off, alen, m); 439 return buffer_clen(m); /*(length of replacement authority)*/ 440 } 441 442 443 /* (future: might move to http-header-glue.c) */ 444 static size_t http_header_remap_urlpath (buffer *b, size_t off, http_header_remap_opts *remap_hdrs, int is_req) 445 { 446 const array *urlpaths = remap_hdrs->urlpaths; 447 if (urlpaths) { 448 const char * const s = b->ptr+off; 449 const size_t plen = buffer_clen(b) - off; /*(urlpath len)*/ 450 if (is_req) { /* request */ 451 for (size_t i = 0, used = urlpaths->used; i < used; ++i) { 452 const data_string * const ds = (data_string *)urlpaths->data[i]; 453 const size_t mlen = buffer_clen(&ds->key); 454 if (mlen <= plen && 0 == memcmp(s, ds->key.ptr, mlen)) { 455 if (NULL == remap_hdrs->forwarded_urlpath) 456 remap_hdrs->forwarded_urlpath = ds; 457 buffer_substr_replace(b, off, mlen, &ds->value); 458 return buffer_clen(&ds->value);/*(replacement len)*/ 459 } 460 } 461 } 462 else { /* response; perform reverse map */ 463 if (NULL != remap_hdrs->forwarded_urlpath) { 464 const data_string * const ds = remap_hdrs->forwarded_urlpath; 465 const size_t mlen = buffer_clen(&ds->value); 466 if (mlen <= plen && 0 == memcmp(s, ds->value.ptr, mlen)) { 467 buffer_substr_replace(b, off, mlen, &ds->key); 468 return buffer_clen(&ds->key); /*(replacement len)*/ 469 } 470 } 471 for (size_t i = 0, used = urlpaths->used; i < used; ++i) { 472 const data_string * const ds = (data_string *)urlpaths->data[i]; 473 const size_t mlen = buffer_clen(&ds->value); 474 if (mlen <= plen && 0 == memcmp(s, ds->value.ptr, mlen)) { 475 buffer_substr_replace(b, off, mlen, &ds->key); 476 return buffer_clen(&ds->key); /*(replacement len)*/ 477 } 478 } 479 } 480 } 481 return 0; 482 } 483 484 485 /* (future: might move to http-header-glue.c) */ 486 static void http_header_remap_uri (buffer *b, size_t off, http_header_remap_opts *remap_hdrs, int is_req) 487 { 488 /* find beginning of URL-path (might be preceded by scheme://authority 489 * (caller should make sure any leading whitespace is prior to offset) */ 490 if (b->ptr[off] != '/') { 491 char *s = b->ptr+off; 492 size_t alen; /*(authority len (host len))*/ 493 size_t slen; /*(scheme len)*/ 494 const buffer *m; 495 /* skip over scheme and authority of URI to find beginning of URL-path 496 * (value might conceivably be relative URL-path instead of URI) */ 497 if (NULL == (s = strchr(s, ':')) || s[1] != '/' || s[2] != '/') return; 498 slen = s - (b->ptr+off); 499 s += 3; 500 off = (size_t)(s - b->ptr); 501 if (NULL != (s = strchr(s, '/'))) { 502 alen = (size_t)(s - b->ptr) - off; 503 if (0 == alen) return; /*(empty authority, e.g. "http:///")*/ 504 } 505 else { 506 alen = buffer_clen(b) - off; 507 if (0 == alen) return; /*(empty authority, e.g. "http:///")*/ 508 buffer_append_char(b, '/'); 509 } 510 511 /* remap authority (if configured) and set offset to url-path */ 512 m = http_header_remap_host_match(b, off, remap_hdrs, is_req, alen); 513 if (NULL != m) { 514 if (remap_hdrs->https_remap 515 && (is_req ? 5==slen && 0==memcmp(b->ptr+off-slen-3,"https",5) 516 : 4==slen && 0==memcmp(b->ptr+off-slen-3,"http",4))){ 517 if (is_req) { 518 memcpy(b->ptr+off-slen-3+4,"://",3); /*("https"=>"http")*/ 519 --off; 520 ++alen; 521 } 522 else {/*(!is_req)*/ 523 memcpy(b->ptr+off-slen-3+4,"s://",4); /*("http" =>"https")*/ 524 ++off; 525 --alen; 526 } 527 } 528 buffer_substr_replace(b, off, alen, m); 529 alen = buffer_clen(m);/*(length of replacement authority)*/ 530 } 531 off += alen; 532 } 533 534 /* remap URLs (if configured) */ 535 http_header_remap_urlpath(b, off, remap_hdrs, is_req); 536 } 537 538 539 /* (future: might move to http-header-glue.c) */ 540 static void http_header_remap_setcookie (buffer *b, size_t off, http_header_remap_opts *remap_hdrs) 541 { 542 /* Given the special-case of Set-Cookie and the (too) loosely restricted 543 * characters allowed, for best results, the Set-Cookie value should be the 544 * entire string in b from offset to end of string. In response headers, 545 * lighttpd may concatenate multiple Set-Cookie headers into single entry 546 * in r->resp_headers, separated by "\r\nSet-Cookie: " */ 547 for (char *s = b->ptr+off, *e; *s; s = e) { 548 size_t len; 549 { 550 while (*s != ';' && *s != '\n' && *s != '\0') ++s; 551 if (*s == '\n') { 552 /*(include +1 for '\n', but leave ' ' for ++s below)*/ 553 s += sizeof("Set-Cookie:"); 554 } 555 if ('\0' == *s) return; 556 do { ++s; } while (*s == ' ' || *s == '\t'); 557 if ('\0' == *s) return; 558 e = s+1; 559 if ('=' == *s) continue; 560 /*(interested only in Domain and Path attributes)*/ 561 while (*e != '=' && *e != '\0') ++e; 562 if ('\0' == *e) return; 563 ++e; 564 switch ((int)(e - s - 1)) { 565 case 4: 566 if (buffer_eq_icase_ssn(s, "path", 4)) { 567 if (*e == '"') ++e; 568 if (*e != '/') continue; 569 off = (size_t)(e - b->ptr); 570 len = http_header_remap_urlpath(b, off, remap_hdrs, 0); 571 e = b->ptr+off+len; /*(b may have been reallocated)*/ 572 continue; 573 } 574 break; 575 case 6: 576 if (buffer_eq_icase_ssn(s, "domain", 6)) { 577 size_t alen = 0; 578 if (*e == '"') ++e; 579 if (*e == '.') ++e; 580 if (*e == ';') continue; 581 off = (size_t)(e - b->ptr); 582 for (char c; (c = e[alen]) != ';' && c != ' ' && c != '\t' 583 && c != '\r' && c != '\0'; ++alen); 584 len = http_header_remap_host(b, off, remap_hdrs, 0, alen); 585 e = b->ptr+off+len; /*(b may have been reallocated)*/ 586 continue; 587 } 588 break; 589 default: 590 break; 591 } 592 } 593 } 594 } 595 596 597 static void buffer_append_string_backslash_escaped(buffer *b, const char *s, size_t len) { 598 /* (future: might move to buffer.c) */ 599 size_t j = 0; 600 char * const p = buffer_string_prepare_append(b, len*2 + 4); 601 602 for (size_t i = 0; i < len; ++i) { 603 int c = s[i]; 604 if (c == '"' || c == '\\' || c == 0x7F || (c < 0x20 && c != '\t')) 605 p[j++] = '\\'; 606 p[j++] = c; 607 } 608 609 buffer_commit(b, j); 610 } 611 612 static void proxy_set_Forwarded(connection * const con, request_st * const r, const unsigned int flags) { 613 buffer *b = NULL; 614 buffer * const efor = (proxy_check_extforward) 615 ? http_header_env_get(r, CONST_STR_LEN("_L_EXTFORWARD_ACTUAL_FOR")) 616 : NULL; 617 int semicolon = 0; 618 619 /* note: set "Forwarded" prior to updating X-Forwarded-For (below) */ 620 621 if (flags) 622 b = http_header_request_get(r, HTTP_HEADER_FORWARDED, CONST_STR_LEN("Forwarded")); 623 624 if (flags && NULL == b) { 625 const buffer *xff = 626 http_header_request_get(r, HTTP_HEADER_X_FORWARDED_FOR, CONST_STR_LEN("X-Forwarded-For")); 627 b = http_header_request_set_ptr(r, HTTP_HEADER_FORWARDED, 628 CONST_STR_LEN("Forwarded")); 629 if (NULL != xff) { 630 /* use X-Forwarded-For contents to seed Forwarded */ 631 char *s = xff->ptr; 632 size_t used = buffer_clen(xff); 633 for (size_t i=0, j, ipv6; i < used; ++i) { 634 while (s[i] == ' ' || s[i] == '\t' || s[i] == ',') ++i; 635 if (s[i] == '\0') break; 636 j = i; 637 do { 638 ++i; 639 } while (s[i]!=' ' && s[i]!='\t' && s[i]!=',' && s[i]!='\0'); 640 /* over-simplified test expecting only IPv4 or IPv6 addresses, 641 * (not expecting :port, so treat existence of colon as IPv6, 642 * and not expecting unix paths, especially not containing ':') 643 * quote all strings, backslash-escape since IPs not validated*/ 644 ipv6 = (NULL != memchr(s+j, ':', i-j)); /*(over-simplified) */ 645 ipv6 646 ? buffer_append_string_len(b, CONST_STR_LEN("for=\"[")) 647 : buffer_append_string_len(b, CONST_STR_LEN("for=\"")); 648 buffer_append_string_backslash_escaped(b, s+j, i-j); 649 ipv6 650 ? buffer_append_string_len(b, CONST_STR_LEN("]\", ")) 651 : buffer_append_string_len(b, CONST_STR_LEN("\", ")); 652 } 653 } 654 } else if (flags) { /*(NULL != b)*/ 655 buffer_append_string_len(b, CONST_STR_LEN(", ")); 656 } 657 658 if (flags & PROXY_FORWARDED_FOR) { 659 int family = sock_addr_get_family(&con->dst_addr); 660 buffer_append_string_len(b, CONST_STR_LEN("for=")); 661 if (NULL != efor) { 662 /* over-simplified test expecting only IPv4 or IPv6 addresses, 663 * (not expecting :port, so treat existence of colon as IPv6, 664 * and not expecting unix paths, especially not containing ':') 665 * quote all strings and backslash-escape since IPs not validated 666 * (should be IP from original con->dst_addr_buf, 667 * so trustable and without :port) */ 668 int ipv6 = (NULL != strchr(efor->ptr, ':')); 669 ipv6 670 ? buffer_append_string_len(b, CONST_STR_LEN("\"[")) 671 : buffer_append_char(b, '"'); 672 buffer_append_string_backslash_escaped(b, BUF_PTR_LEN(efor)); 673 ipv6 674 ? buffer_append_string_len(b, CONST_STR_LEN("]\"")) 675 : buffer_append_char(b, '"'); 676 } else if (family == AF_INET) { 677 /*(Note: if :port is added, then must be quoted-string: 678 * e.g. for="...:port")*/ 679 buffer_append_string_buffer(b, &con->dst_addr_buf); 680 } else if (family == AF_INET6) { 681 buffer_append_str3(b, CONST_STR_LEN("\"["), 682 BUF_PTR_LEN(&con->dst_addr_buf), 683 CONST_STR_LEN("]\"")); 684 } else { 685 buffer_append_char(b, '"'); 686 buffer_append_string_backslash_escaped( 687 b, BUF_PTR_LEN(&con->dst_addr_buf)); 688 buffer_append_char(b, '"'); 689 } 690 semicolon = 1; 691 } 692 693 if (flags & PROXY_FORWARDED_BY) { 694 int family = sock_addr_get_family(&con->srv_socket->addr); 695 /* Note: getsockname() and inet_ntop() are expensive operations. 696 * (recommendation: do not to enable by=... unless required) 697 * future: might use con->srv_socket->srv_token if addr is not 698 * INADDR_ANY or in6addr_any, but must omit optional :port 699 * from con->srv_socket->srv_token for consistency */ 700 701 if (semicolon) buffer_append_char(b, ';'); 702 buffer_append_string_len(b, CONST_STR_LEN("by=\"")); 703 #ifdef HAVE_SYS_UN_H 704 /* special-case: might need to encode unix domain socket path */ 705 if (family == AF_UNIX) { 706 buffer_append_string_backslash_escaped( 707 b, BUF_PTR_LEN(con->srv_socket->srv_token)); 708 } 709 else 710 #endif 711 { 712 sock_addr addr; 713 socklen_t addrlen = sizeof(addr); 714 if (0 == getsockname(con->fd,(struct sockaddr *)&addr, &addrlen)) { 715 sock_addr_stringify_append_buffer(b, &addr); 716 } 717 } 718 buffer_append_char(b, '"'); 719 semicolon = 1; 720 } 721 722 if (flags & PROXY_FORWARDED_PROTO) { 723 const buffer * const eproto = (proxy_check_extforward) 724 ? http_header_env_get(r, CONST_STR_LEN("_L_EXTFORWARD_ACTUAL_PROTO")) 725 : NULL; 726 /* expecting "http" or "https" 727 * (not checking if quoted-string and encoding needed) */ 728 if (semicolon) buffer_append_char(b, ';'); 729 if (NULL != eproto) { 730 buffer_append_str2(b, CONST_STR_LEN("proto="), BUF_PTR_LEN(eproto)); 731 } else if (con->srv_socket->is_ssl) { 732 buffer_append_string_len(b, CONST_STR_LEN("proto=https")); 733 } else { 734 buffer_append_string_len(b, CONST_STR_LEN("proto=http")); 735 } 736 semicolon = 1; 737 } 738 739 if (flags & PROXY_FORWARDED_HOST) { 740 const buffer * const ehost = (proxy_check_extforward) 741 ? http_header_env_get(r, CONST_STR_LEN("_L_EXTFORWARD_ACTUAL_HOST")) 742 : NULL; 743 if (NULL != ehost) { 744 if (semicolon) 745 buffer_append_char(b, ';'); 746 buffer_append_string_len(b, CONST_STR_LEN("host=\"")); 747 buffer_append_string_backslash_escaped( 748 b, BUF_PTR_LEN(ehost)); 749 buffer_append_char(b, '"'); 750 semicolon = 1; 751 } else if (r->http_host && !buffer_is_blank(r->http_host)) { 752 if (semicolon) 753 buffer_append_char(b, ';'); 754 buffer_append_string_len(b, CONST_STR_LEN("host=\"")); 755 buffer_append_string_backslash_escaped( 756 b, BUF_PTR_LEN(r->http_host)); 757 buffer_append_char(b, '"'); 758 semicolon = 1; 759 } 760 } 761 762 if (flags & PROXY_FORWARDED_REMOTE_USER) { 763 const buffer *remote_user = 764 http_header_env_get(r, CONST_STR_LEN("REMOTE_USER")); 765 if (NULL != remote_user) { 766 if (semicolon) 767 buffer_append_char(b, ';'); 768 buffer_append_string_len(b, CONST_STR_LEN("remote_user=\"")); 769 buffer_append_string_backslash_escaped( 770 b, BUF_PTR_LEN(remote_user)); 771 buffer_append_char(b, '"'); 772 /*semicolon = 1;*/ 773 } 774 } 775 776 /* legacy X-* headers, including X-Forwarded-For */ 777 778 b = (NULL != efor) ? efor : &con->dst_addr_buf; 779 http_header_request_append(r, HTTP_HEADER_X_FORWARDED_FOR, 780 CONST_STR_LEN("X-Forwarded-For"), 781 BUF_PTR_LEN(b)); 782 783 b = r->http_host; 784 if (b && !buffer_is_blank(b)) { 785 http_header_request_set(r, HTTP_HEADER_OTHER, 786 CONST_STR_LEN("X-Host"), 787 BUF_PTR_LEN(b)); 788 http_header_request_set(r, HTTP_HEADER_OTHER, 789 CONST_STR_LEN("X-Forwarded-Host"), 790 BUF_PTR_LEN(b)); 791 } 792 793 b = &r->uri.scheme; 794 http_header_request_set(r, HTTP_HEADER_X_FORWARDED_PROTO, 795 CONST_STR_LEN("X-Forwarded-Proto"), 796 BUF_PTR_LEN(b)); 797 } 798 799 800 static handler_t proxy_stdin_append(gw_handler_ctx *hctx) { 801 /*handler_ctx *hctx = (handler_ctx *)gwhctx;*/ 802 chunkqueue * const req_cq = &hctx->r->reqbody_queue; 803 const off_t req_cqlen = chunkqueue_length(req_cq); 804 if (req_cqlen) { 805 /* XXX: future: use http_chunk_len_append() */ 806 buffer * const tb = hctx->r->tmp_buf; 807 buffer_clear(tb); 808 buffer_append_uint_hex_lc(tb, (uintmax_t)req_cqlen); 809 buffer_append_string_len(tb, CONST_STR_LEN("\r\n")); 810 811 const off_t len = (off_t)buffer_clen(tb) 812 + 2 /*(+2 end chunk "\r\n")*/ 813 + req_cqlen; 814 if (-1 != hctx->wb_reqlen) 815 hctx->wb_reqlen += (hctx->wb_reqlen >= 0) ? len : -len; 816 817 (chunkqueue_is_empty(&hctx->wb) || hctx->wb.first->type == MEM_CHUNK) 818 /* else FILE_CHUNK for temp file */ 819 ? chunkqueue_append_mem(&hctx->wb, BUF_PTR_LEN(tb)) 820 : chunkqueue_append_mem_min(&hctx->wb, BUF_PTR_LEN(tb)); 821 chunkqueue_steal(&hctx->wb, req_cq, req_cqlen); 822 823 chunkqueue_append_mem_min(&hctx->wb, CONST_STR_LEN("\r\n")); 824 } 825 826 if (hctx->wb.bytes_in == hctx->wb_reqlen) {/*hctx->r->reqbody_length >= 0*/ 827 /* terminate STDIN */ 828 chunkqueue_append_mem(&hctx->wb, CONST_STR_LEN("0\r\n\r\n")); 829 hctx->wb_reqlen += (int)sizeof("0\r\n\r\n"); 830 } 831 832 return HANDLER_GO_ON; 833 } 834 835 836 static handler_t proxy_create_env(gw_handler_ctx *gwhctx) { 837 handler_ctx *hctx = (handler_ctx *)gwhctx; 838 request_st * const r = hctx->gw.r; 839 const int remap_headers = (NULL != hctx->conf.header.urlpaths 840 || NULL != hctx->conf.header.hosts_request); 841 size_t rsz = (size_t)(r->read_queue.bytes_out - hctx->gw.wb.bytes_in); 842 if (rsz >= 65536) rsz = r->rqst_header_len; 843 buffer * const b = chunkqueue_prepend_buffer_open_sz(&hctx->gw.wb, rsz); 844 845 /* build header */ 846 847 /* request line */ 848 const buffer * const m = 849 http_method_buf(!r->h2_connect_ext 850 ? r->http_method 851 : HTTP_METHOD_GET); /*(translate HTTP/2 CONNECT ext)*/ 852 buffer_append_str3(b, 853 BUF_PTR_LEN(m), 854 CONST_STR_LEN(" "), 855 BUF_PTR_LEN(&r->target)); 856 if (remap_headers) 857 http_header_remap_uri(b, buffer_clen(b) - buffer_clen(&r->target), 858 &hctx->conf.header, 1); 859 860 buffer_append_string_len(b, !hctx->conf.header.force_http10 861 ? " HTTP/1.1" : " HTTP/1.0", 862 sizeof(" HTTP/1.1")-1); 863 864 if (hctx->conf.replace_http_host && !buffer_is_blank(hctx->gw.host->id)) { 865 if (hctx->gw.conf.debug > 1) { 866 log_error(r->conf.errh, __FILE__, __LINE__, 867 "proxy - using \"%s\" as HTTP Host", hctx->gw.host->id->ptr); 868 } 869 buffer_append_str2(b, CONST_STR_LEN("\r\nHost: "), 870 BUF_PTR_LEN(hctx->gw.host->id)); 871 } else if (r->http_host && !buffer_is_unset(r->http_host)) { 872 buffer_append_str2(b, CONST_STR_LEN("\r\nHost: "), 873 BUF_PTR_LEN(r->http_host)); 874 if (remap_headers) { 875 size_t alen = buffer_clen(r->http_host); 876 http_header_remap_host(b, buffer_clen(b) - alen, &hctx->conf.header, 1, alen); 877 } 878 } else { 879 /* no Host header available; must send HTTP/1.0 request */ 880 b->ptr[b->used-2] = '0'; /*(overwrite end of request line)*/ 881 } 882 883 if (r->reqbody_length > 0 884 || (0 == r->reqbody_length 885 && !http_method_get_or_head(r->http_method))) { 886 /* set Content-Length if client sent Transfer-Encoding: chunked 887 * and not streaming to backend (request body has been fully received) */ 888 const buffer *vb = http_header_request_get(r, HTTP_HEADER_CONTENT_LENGTH, CONST_STR_LEN("Content-Length")); 889 if (NULL == vb) { 890 buffer_append_int( 891 http_header_request_set_ptr(r, HTTP_HEADER_CONTENT_LENGTH, 892 CONST_STR_LEN("Content-Length")), 893 r->reqbody_length); 894 } 895 } 896 else if (r->h2_connect_ext) { 897 } 898 else if (-1 == r->reqbody_length 899 && (r->conf.stream_request_body 900 & (FDEVENT_STREAM_REQUEST | FDEVENT_STREAM_REQUEST_BUFMIN))) { 901 if (__builtin_expect( (hctx->conf.header.force_http10), 0)) 902 return http_response_reqbody_read_error(r, 411); 903 hctx->gw.stdin_append = proxy_stdin_append; /* stream chunked body */ 904 buffer_append_string_len(b, CONST_STR_LEN("\r\nTransfer-Encoding: chunked")); 905 } 906 907 /* "Forwarded" and legacy X- headers */ 908 proxy_set_Forwarded(r->con, r, hctx->conf.forwarded); 909 910 /* request header */ 911 const buffer *connhdr = NULL; 912 const buffer *te = NULL; 913 const buffer *upgrade = NULL; 914 for (size_t i = 0, used = r->rqst_headers.used; i < used; ++i) { 915 const data_string * const ds = (data_string *)r->rqst_headers.data[i]; 916 switch (ds->ext) { 917 default: 918 break; 919 case HTTP_HEADER_HOST: 920 continue; /*(handled further above)*/ 921 case HTTP_HEADER_OTHER: 922 if (__builtin_expect( ('p' == (ds->key.ptr[0] | 0x20)), 0)) { 923 if (buffer_eq_icase_slen(&ds->key, CONST_STR_LEN("Proxy-Connection"))) continue; 924 /* Do not emit HTTP_PROXY in environment. 925 * Some executables use HTTP_PROXY to configure 926 * outgoing proxy. See also https://httpoxy.org/ */ 927 if (buffer_eq_icase_slen(&ds->key, CONST_STR_LEN("Proxy"))) continue; 928 } 929 break; 930 case HTTP_HEADER_TE: 931 if (hctx->conf.header.force_http10 || r->http_version == HTTP_VERSION_1_0) continue; 932 /* ignore if not exactly "trailers" */ 933 if (!buffer_eq_icase_slen(&ds->value, CONST_STR_LEN("trailers"))) continue; 934 /*if (!buffer_is_blank(&ds->value)) te = &ds->value;*/ 935 te = &ds->value; /*("trailers")*/ 936 break; 937 case HTTP_HEADER_UPGRADE: 938 if (hctx->conf.header.force_http10 939 || (r->http_version != HTTP_VERSION_1_1 && !r->h2_connect_ext)) 940 continue; 941 if (!hctx->conf.header.upgrade) continue; 942 if (!buffer_is_blank(&ds->value)) upgrade = &ds->value; 943 break; 944 case HTTP_HEADER_CONNECTION: 945 connhdr = &ds->value; 946 continue; 947 case HTTP_HEADER_SET_COOKIE: 948 continue; /*(response header only; avoid accidental reflection)*/ 949 } 950 951 const uint32_t klen = buffer_clen(&ds->key); 952 const uint32_t vlen = buffer_clen(&ds->value); 953 if (0 == klen || 0 == vlen) continue; 954 char * restrict s = buffer_extend(b, klen+vlen+4); 955 s[0] = '\r'; 956 s[1] = '\n'; 957 memcpy(s+2, ds->key.ptr, klen); 958 s += 2+klen; 959 s[0] = ':'; 960 s[1] = ' '; 961 memcpy(s+2, ds->value.ptr, vlen); 962 963 if (!remap_headers) continue; 964 965 /* check for hdrs for which to remap URIs in-place after append to b */ 966 967 switch (klen) { 968 default: 969 continue; 970 #if 0 /* "URI" is HTTP response header (non-standard; historical in Apache) */ 971 case 3: 972 if (ds->ext == HTTP_HEADER_OTHER && buffer_eq_icase_slen(&ds->key, CONST_STR_LEN("URI"))) break; 973 continue; 974 #endif 975 #if 0 /* "Location" is HTTP response header */ 976 case 8: 977 if (ds->ext == HTTP_HEADER_LOCATION) break; 978 continue; 979 #endif 980 case 11: /* "Destination" is WebDAV request header */ 981 if (ds->ext == HTTP_HEADER_OTHER && buffer_eq_icase_slen(&ds->key, CONST_STR_LEN("Destination"))) break; 982 continue; 983 case 16: /* "Content-Location" may be HTTP request or response header */ 984 if (ds->ext == HTTP_HEADER_CONTENT_LOCATION) break; 985 continue; 986 } 987 988 http_header_remap_uri(b, buffer_clen(b) - vlen, &hctx->conf.header, 1); 989 } 990 991 if (connhdr && !hctx->conf.header.force_http10 && r->http_version >= HTTP_VERSION_1_1 992 && !buffer_eq_icase_slen(connhdr, CONST_STR_LEN("close"))) { 993 /* mod_proxy always sends Connection: close to backend */ 994 buffer_append_string_len(b, CONST_STR_LEN("\r\nConnection: close")); 995 /* (future: might be pedantic and also check Connection header for each 996 * token using http_header_str_contains_token() */ 997 if (te) 998 buffer_append_string_len(b, CONST_STR_LEN(", te")); 999 if (upgrade) 1000 buffer_append_string_len(b, CONST_STR_LEN(", upgrade")); 1001 buffer_append_string_len(b, CONST_STR_LEN("\r\n\r\n")); 1002 } 1003 else if (r->h2_connect_ext) { 1004 /* https://datatracker.ietf.org/doc/html/rfc6455#section-4.1 1005 * 7. The request MUST include a header field with the name 1006 * |Sec-WebSocket-Key|. The value of this header field MUST be a 1007 * nonce consisting of a randomly selected 16-byte value that has 1008 * been base64-encoded (see Section 4 of [RFC4648]). The nonce 1009 * MUST be selected randomly for each connection. 1010 * Note: Sec-WebSocket-Key is not used in RFC8441; 1011 * include Sec-WebSocket-Key for HTTP/1.1 compatibility; 1012 * !!not random!! base64-encoded "0000000000000000" */ 1013 if (!http_header_request_get(r, HTTP_HEADER_OTHER, 1014 CONST_STR_LEN("Sec-WebSocket-Key"))) 1015 buffer_append_string_len(b, CONST_STR_LEN( 1016 "\r\nSec-WebSocket-Key: MDAwMDAwMDAwMDAwMDAwMA==")); 1017 buffer_append_string_len(b, CONST_STR_LEN( 1018 "\r\nUpgrade: websocket" 1019 "\r\nConnection: close, upgrade\r\n\r\n")); 1020 } 1021 else /* mod_proxy always sends Connection: close to backend */ 1022 buffer_append_string_len(b, CONST_STR_LEN("\r\nConnection: close\r\n\r\n")); 1023 1024 hctx->gw.wb_reqlen = buffer_clen(b); 1025 chunkqueue_prepend_buffer_commit(&hctx->gw.wb); 1026 1027 if (r->reqbody_length) { 1028 if (r->reqbody_length > 0) 1029 hctx->gw.wb_reqlen += r->reqbody_length; /* total req size */ 1030 else /* as-yet-unknown total request size (Transfer-Encoding: chunked)*/ 1031 hctx->gw.wb_reqlen = -hctx->gw.wb_reqlen; 1032 if (hctx->gw.stdin_append == proxy_stdin_append) 1033 proxy_stdin_append(&hctx->gw); 1034 else 1035 chunkqueue_append_chunkqueue(&hctx->gw.wb, &r->reqbody_queue); 1036 } 1037 1038 plugin_stats_inc("proxy.requests"); 1039 return HANDLER_GO_ON; 1040 } 1041 1042 1043 static handler_t proxy_create_env_connect(gw_handler_ctx *gwhctx) { 1044 handler_ctx *hctx = (handler_ctx *)gwhctx; 1045 request_st * const r = hctx->gw.r; 1046 r->http_status = 200; /* OK */ 1047 r->resp_body_started = 1; 1048 gw_set_transparent(&hctx->gw); 1049 http_response_upgrade_read_body_unknown(r); 1050 1051 plugin_stats_inc("proxy.requests"); 1052 return HANDLER_GO_ON; 1053 } 1054 1055 1056 static handler_t proxy_response_headers(request_st * const r, struct http_response_opts_t *opts) { 1057 /* response headers just completed */ 1058 handler_ctx *hctx = (handler_ctx *)opts->pdata; 1059 http_header_remap_opts * const remap_hdrs = &hctx->conf.header; 1060 1061 if (light_btst(r->resp_htags, HTTP_HEADER_UPGRADE)) { 1062 if (remap_hdrs->upgrade && r->http_status == 101) { 1063 /* 101 Switching Protocols; transition to transparent proxy */ 1064 if (r->h2_connect_ext) { 1065 r->http_status = 200; /* OK (response status for CONNECT) */ 1066 http_header_response_unset(r, HTTP_HEADER_UPGRADE, 1067 CONST_STR_LEN("Upgrade")); 1068 http_header_response_unset(r, HTTP_HEADER_OTHER, 1069 CONST_STR_LEN("Sec-WebSocket-Accept")); 1070 } 1071 gw_set_transparent(&hctx->gw); 1072 http_response_upgrade_read_body_unknown(r); 1073 } 1074 else { 1075 light_bclr(r->resp_htags, HTTP_HEADER_UPGRADE); 1076 #if 0 1077 /* preserve prior questionable behavior; likely broken behavior 1078 * anyway if backend thinks connection is being upgraded but client 1079 * does not receive Connection: upgrade */ 1080 http_header_response_unset(r, HTTP_HEADER_UPGRADE, 1081 CONST_STR_LEN("Upgrade")) 1082 #endif 1083 } 1084 } 1085 else if (__builtin_expect( (r->h2_connect_ext != 0), 0) 1086 && r->http_status < 300) { 1087 /*(not handling other 1xx intermediate responses here; not expected)*/ 1088 http_response_body_clear(r, 0); 1089 r->handler_module = NULL; 1090 r->http_status = 405; /* Method Not Allowed */ 1091 return HANDLER_FINISHED; 1092 } 1093 1094 /* rewrite paths, if needed */ 1095 1096 if (NULL == remap_hdrs->urlpaths && NULL == remap_hdrs->hosts_response) 1097 return HANDLER_GO_ON; 1098 1099 buffer *vb; 1100 if (light_btst(r->resp_htags, HTTP_HEADER_LOCATION)) { 1101 vb = http_header_response_get(r, HTTP_HEADER_LOCATION, 1102 CONST_STR_LEN("Location")); 1103 if (vb) http_header_remap_uri(vb, 0, remap_hdrs, 0); 1104 } 1105 if (light_btst(r->resp_htags, HTTP_HEADER_CONTENT_LOCATION)) { 1106 vb = http_header_response_get(r, HTTP_HEADER_CONTENT_LOCATION, 1107 CONST_STR_LEN("Content-Location")); 1108 if (vb) http_header_remap_uri(vb, 0, remap_hdrs, 0); 1109 } 1110 if (light_btst(r->resp_htags, HTTP_HEADER_SET_COOKIE)) { 1111 vb = http_header_response_get(r, HTTP_HEADER_SET_COOKIE, 1112 CONST_STR_LEN("Set-Cookie")); 1113 if (vb) http_header_remap_setcookie(vb, 0, remap_hdrs); 1114 } 1115 1116 return HANDLER_GO_ON; 1117 } 1118 1119 static handler_t mod_proxy_check_extension(request_st * const r, void *p_d) { 1120 plugin_data *p = p_d; 1121 handler_t rc; 1122 1123 if (NULL != r->handler_module) return HANDLER_GO_ON; 1124 1125 mod_proxy_patch_config(r, p); 1126 if (NULL == p->conf.gw.exts) return HANDLER_GO_ON; 1127 1128 rc = gw_check_extension(r, (gw_plugin_data *)p, 1, sizeof(handler_ctx)); 1129 if (HANDLER_GO_ON != rc) return rc; 1130 1131 if (r->handler_module == p->self) { 1132 handler_ctx *hctx = r->plugin_ctx[p->id]; 1133 hctx->gw.create_env = proxy_create_env; 1134 hctx->gw.response = chunk_buffer_acquire(); 1135 hctx->gw.opts.backend = BACKEND_PROXY; 1136 hctx->gw.opts.pdata = hctx; 1137 hctx->gw.opts.headers = proxy_response_headers; 1138 1139 hctx->conf = p->conf; /*(copies struct)*/ 1140 hctx->conf.header.http_host = r->http_host; 1141 hctx->conf.header.upgrade &= 1142 (r->http_version == HTTP_VERSION_1_1 || r->h2_connect_ext); 1143 /* mod_proxy currently sends all backend requests as http. 1144 * https-remap is a flag since it might not be needed if backend 1145 * honors Forwarded or X-Forwarded-Proto headers, e.g. by using 1146 * lighttpd mod_extforward or similar functionality in backend*/ 1147 if (hctx->conf.header.https_remap) { 1148 hctx->conf.header.https_remap = 1149 buffer_is_equal_string(&r->uri.scheme, CONST_STR_LEN("https")); 1150 } 1151 1152 if (r->http_method == HTTP_METHOD_CONNECT) { 1153 /*(note: not requiring HTTP/1.1 due to too many non-compliant 1154 * clients such as 'openssl s_client')*/ 1155 if (r->h2_connect_ext 1156 && (hctx->conf.header.connect_method = 1157 hctx->conf.header.upgrade)) { /*(405 if not set)*/ 1158 /*(not bothering to check (!hctx->conf.header.force_http10))*/ 1159 /*hctx->gw.create_env = proxy_create_env;*/ /*(preserve)*/ 1160 } 1161 else if (hctx->conf.header.connect_method) { 1162 hctx->gw.create_env = proxy_create_env_connect; 1163 } 1164 else { 1165 r->http_status = 405; /* Method Not Allowed */ 1166 r->handler_module = NULL; 1167 return HANDLER_FINISHED; 1168 } 1169 } 1170 } 1171 1172 return HANDLER_GO_ON; 1173 } 1174 1175 1176 int mod_proxy_plugin_init(plugin *p); 1177 int mod_proxy_plugin_init(plugin *p) { 1178 p->version = LIGHTTPD_VERSION_ID; 1179 p->name = "proxy"; 1180 1181 p->init = mod_proxy_init; 1182 p->cleanup = mod_proxy_free; 1183 p->set_defaults = mod_proxy_set_defaults; 1184 p->handle_request_reset = gw_handle_request_reset; 1185 p->handle_uri_clean = mod_proxy_check_extension; 1186 p->handle_subrequest = gw_handle_subrequest; 1187 p->handle_trigger = gw_handle_trigger; 1188 p->handle_waitpid = gw_handle_waitpid_cb; 1189 1190 return 0; 1191 } 1192