[mod_proxy] pass Content-Length to backend if > 0
[lighttpd.git] / src / mod_proxy.c
blob97594460e23cd80c4aafc5f22dcc004cc0e7bf72
1 #include "first.h"
3 #include <string.h>
4 #include <stdlib.h>
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"
16 /**
18 * HTTP reverse proxy
20 * TODO: - HTTP/1.1
21 * - HTTP/1.1 persistent connection with upstream servers
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;
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;
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;
54 http_header_remap_opts header;
55 } plugin_config;
57 typedef struct {
58 PLUGIN_DATA;
59 plugin_config **config_storage;
61 plugin_config conf;
62 } plugin_data;
64 static int proxy_check_extforward;
66 typedef struct {
67 gw_handler_ctx gw;
68 http_response_opts opts;
69 plugin_config conf;
70 } handler_ctx;
73 INIT_FUNC(mod_proxy_init) {
74 plugin_data *p;
76 p = calloc(1, sizeof(*p));
78 return p;
82 FREE_FUNC(mod_proxy_free) {
83 plugin_data *p = p_d;
85 UNUSED(srv);
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];
92 if (NULL == s) continue;
94 array_free(s->forwarded_params);
95 array_free(s->header_params);
97 /*assert(0 == offsetof(s->gw));*/
98 gw_plugin_config_free(&s->gw);
99 /*free(s);*//*free'd by gw_plugin_config_free()*/
101 free(p->config_storage);
104 free(p);
106 return HANDLER_GO_ON;
109 SETDEFAULTS_FUNC(mod_proxy_set_defaults) {
110 plugin_data *p = p_d;
111 data_unset *du;
112 size_t i = 0;
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 }
125 p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config *));
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;
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();
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;
147 p->config_storage[i] = s;
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;
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;
158 du = array_get_element(config->value, "proxy.balance");
159 if (!gw_set_defaults_balance(srv, &s->gw, du)) {
160 return HANDLER_ERROR;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
260 if (buffer_is_equal_string(da->key, CONST_STR_LEN("map-urlpath"))) {
261 s->header.urlpaths = da->value;
263 else if (buffer_is_equal_string(da->key, CONST_STR_LEN("map-host-request"))) {
264 s->header.hosts_request = da->value;
266 else if (buffer_is_equal_string(da->key, CONST_STR_LEN("map-host-response"))) {
267 s->header.hosts_response = da->value;
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;
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;
285 return HANDLER_GO_ON;
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)
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);
312 if (mlen == alen && 0 == strncasecmp(s, k->ptr, alen)) {
313 if (buffer_is_equal_string(ds->value, CONST_STR_LEN("-"))) {
314 return remap_hdrs->http_host;
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;
326 return NULL;
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)
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)*/
337 buffer_substr_replace(b, off, alen, m);
338 return buffer_string_length(m); /*(length of replacement authority)*/
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)
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)*/
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)*/
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)*/
380 return 0;
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)
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:///")*/
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("/"));
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;
421 else {/*(!is_req)*/
422 memcpy(b->ptr+off-slen-3+4,"s://",4); /*("http" =>"https")*/
423 ++off;
424 --alen;
427 buffer_substr_replace(b, off, alen, m);
428 alen = buffer_string_length(m);/*(length of replacement authority)*/
430 off += alen;
433 /* remap URLs (if configured) */
434 http_header_remap_urlpath(b, off, remap_hdrs, is_req);
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)
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;
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:");
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 (0 == strncasecmp(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;
473 break;
474 case 6:
475 if (0 == strncasecmp(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;
487 break;
488 default:
489 break;
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;
501 buffer_string_prepare_append(b, len*2 + 4);
502 p = b->ptr + buffer_string_length(b);
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;
511 buffer_commit(b, j);
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;
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"));
527 /* note: set "Forwarded" prior to updating X-Forwarded-For (below) */
529 if (flags)
530 b = http_header_request_get(con, HTTP_HEADER_FORWARDED, CONST_STR_LEN("Forwarded"));
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(", "));
570 } else if (flags) { /*(NULL != b)*/
571 buffer_append_string_len(b, CONST_STR_LEN(", "));
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("\""));
605 semicolon = 1;
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 */
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));
625 else
626 #endif
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);
634 buffer_append_string_len(b, CONST_STR_LEN("\""));
635 semicolon = 1;
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"));
650 semicolon = 1;
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;
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;
687 /* legacy X-* headers, including X-Forwarded-For */
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));
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));
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));
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);
721 /* build header */
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"));
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");
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);
749 buffer_append_string_len(b, CONST_STR_LEN("\r\n"));
752 /* "Forwarded" and legacy X- headers */
753 proxy_set_Forwarded(con, hctx->conf.forwarded);
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));
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;
797 vlen = buffer_string_length(ds->value);
798 if (0 == vlen) continue;
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);
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"));
811 if (!remap_headers) continue;
813 /* check for hdrs for which to remap URIs in-place after append to b */
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;
836 http_header_remap_uri(b, buffer_string_length(b) - vlen - 2, &hctx->conf.header, 1);
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"));
844 hctx->gw.wb_reqlen = buffer_string_length(b);
845 chunkqueue_prepend_buffer_commit(hctx->gw.wb);
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;
855 status_counter_inc(srv, CONST_STR_LEN("proxy.requests"));
856 return HANDLER_GO_ON;
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);
868 status_counter_inc(srv, CONST_STR_LEN("proxy.requests"));
869 return HANDLER_GO_ON;
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];
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)*/
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];
896 /* condition didn't match */
897 if (!config_check_cond(srv, con, dc)) continue;
899 /* merge config */
900 for (j = 0; j < dc->value->used; j++) {
901 data_unset *du = dc->value->data[j];
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)*/
923 return 0;
925 #undef PATCH_GW
926 #undef PATCH
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;
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);
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
950 /* rewrite paths, if needed */
952 if (NULL == hctx->conf.header.urlpaths
953 && NULL == hctx->conf.header.hosts_response)
954 return HANDLER_GO_ON;
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);
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);
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);
969 return HANDLER_GO_ON;
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;
976 if (con->mode != DIRECT) return HANDLER_GO_ON;
978 mod_proxy_patch_connection(srv, con, p);
979 if (NULL == p->conf.gw.exts) return HANDLER_GO_ON;
981 rc = gw_check_extension(srv, con, (gw_plugin_data *)p, 1, sizeof(handler_ctx));
982 if (HANDLER_GO_ON != rc) return rc;
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;
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"));
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;
1010 else {
1011 con->http_status = 405; /* Method Not Allowed */
1012 con->mode = DIRECT;
1013 return HANDLER_FINISHED;
1018 return HANDLER_GO_ON;
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");
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;
1036 p->data = NULL;
1038 return 0;