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