6 #include "gw_backend.h"
11 #include "http_header.h"
13 #include "sock_addr.h"
14 #include "status_counter.h"
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
;
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
;
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
49 array
*forwarded_params
;
51 unsigned short replace_http_host
;
52 unsigned int forwarded
;
54 http_header_remap_opts header
;
59 plugin_config
**config_storage
;
64 static int proxy_check_extforward
;
68 http_response_opts opts
;
73 INIT_FUNC(mod_proxy_init
) {
76 p
= calloc(1, sizeof(*p
));
82 FREE_FUNC(mod_proxy_free
) {
87 if (p
->config_storage
) {
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
);
106 return HANDLER_GO_ON
;
109 SETDEFAULTS_FUNC(mod_proxy_set_defaults
) {
110 plugin_data
*p
= p_d
;
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(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
];
131 s
= calloc(1, sizeof(plugin_config
));
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
;
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
;
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"));
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"));
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"));
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
;
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;
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
;
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
307 ? remap_hdrs
->http_host
308 : remap_hdrs
->forwarded_host
;
309 if (NULL
== k
) continue;
310 mlen
= buffer_string_length(k
);
312 if (buffer_eq_icase_ss(s
, alen
, k
->ptr
, mlen
)) {
313 if (buffer_is_equal_string(ds
->value
, CONST_STR_LEN("-"))) {
314 return remap_hdrs
->http_host
;
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
;
321 } /*(else leave authority as-is and stop matching)*/
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
;
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)*/
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)*/
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
);
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:///")*/
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
);
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))){
417 memcpy(b
->ptr
+off
-slen
-3+4,"://",3); /*("https"=>"http")*/
422 memcpy(b
->ptr
+off
-slen
-3+4,"s://",4); /*("http" =>"https")*/
427 buffer_substr_replace(b
, off
, alen
, m
);
428 alen
= buffer_string_length(m
);/*(length of replacement authority)*/
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
) {
449 while (*s
!= ';' && *s
!= '\n' && *s
!= '\0') ++s
;
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;
458 if ('=' == *s
) continue;
459 /*(interested only in Domain and Path attributes)*/
460 while (*e
!= '=' && *e
!= '\0') ++e
;
461 if ('\0' == *e
) return;
463 switch ((int)(e
- s
- 1)) {
465 if (buffer_eq_icase_ssn(s
, "path", 4)) {
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)*/
475 if (buffer_eq_icase_ssn(s
, "domain", 6)) {
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)*/
496 static void buffer_append_string_backslash_escaped(buffer
*b
, const char *s
, size_t len
) {
497 /* (future: might move to buffer.c) */
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
) {
506 if (c
== '"' || c
== '\\' || c
== 0x7F || (c
< 0x20 && c
!= '\t'))
514 static void proxy_set_Forwarded(connection
*con
, const unsigned int flags
) {
515 buffer
*b
= NULL
, *efor
= NULL
, *eproto
= NULL
, *ehost
= NULL
;
518 if (proxy_check_extforward
) {
520 http_header_env_get(con
, CONST_STR_LEN("_L_EXTFORWARD_ACTUAL_FOR"));
522 http_header_env_get(con
, CONST_STR_LEN("_L_EXTFORWARD_ACTUAL_PROTO"));
524 http_header_env_get(con
, CONST_STR_LEN("_L_EXTFORWARD_ACTUAL_HOST"));
527 /* note: set "Forwarded" prior to updating X-Forwarded-For (below) */
530 b
= http_header_request_get(con
, HTTP_HEADER_FORWARDED
, CONST_STR_LEN("Forwarded"));
532 if (flags
&& NULL
== b
) {
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)*/
539 force_assert(NULL
!= b
); /*(not NULL because created directly above)*/
541 b
= http_header_request_get(con
, HTTP_HEADER_FORWARDED
, CONST_STR_LEN("Forwarded"));
544 /* use X-Forwarded-For contents to seed Forwarded */
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;
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("\""));
562 buffer_append_string_len(b
, CONST_STR_LEN("["));
563 buffer_append_string_backslash_escaped(b
, s
+j
, i
-j
);
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="));
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("]\""));
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("\""));
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("\""));
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
));
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("\""));
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"));
648 buffer_append_string_len(b
, CONST_STR_LEN("http"));
653 if (flags
& PROXY_FORWARDED_HOST
) {
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("\""));
662 } else if (!buffer_string_is_empty(con
->request
.http_host
)) {
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("\""));
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
) {
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("\""));
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"),
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"),
699 http_header_request_set(con
, HTTP_HEADER_OTHER
,
700 CONST_STR_LEN("X-Forwarded-Host"),
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"),
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
);
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
);
728 http_header_remap_uri(b
, buffer_string_length(b
) - buffer_string_length(con
->request
.uri
), &hctx
->conf
.header
, 1);
730 buffer_append_string_len(b
, CONST_STR_LEN(" HTTP/1.0\r\n"));
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
);
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"));
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
));
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
);
778 if (buffer_is_equal_caseless_string(ds
->key
, CONST_STR_LEN("Host"))) continue; /*(handled further above)*/
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)*/
785 if (buffer_is_equal_caseless_string(ds
->key
, CONST_STR_LEN("Proxy-Connection"))) continue;
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;
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 */
818 #if 0 /* "URI" is HTTP response header (non-standard; historical in Apache) */
820 if (buffer_is_equal_caseless_string(ds
->key
, CONST_STR_LEN("URI"))) break;
823 #if 0 /* "Location" is HTTP response header */
825 if (buffer_is_equal_caseless_string(ds
->key
, CONST_STR_LEN("Location"))) break;
828 case 11: /* "Destination" is WebDAV request header */
829 if (buffer_is_equal_caseless_string(ds
->key
, CONST_STR_LEN("Destination"))) break;
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;
836 http_header_remap_uri(b
, buffer_string_length(b
) - vlen
- 2, &hctx
->conf
.header
, 1);
840 buffer_append_string_len(b
, CONST_STR_LEN("Connection: close\r\n\r\n"));
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
;
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
) {
879 plugin_config
*s
= p
->config_storage
[0];
885 PATCH_GW(ext_mapping
);
887 PATCH(replace_http_host
);
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;
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"))) {
907 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("proxy.debug"))) {
909 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("proxy.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"))) {
917 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("proxy.header"))) {
918 PATCH(header
); /*(copies struct)*/
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
);
939 con
->response
.htags
&= ~HTTP_HEADER_UPGRADE
;
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"))
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
;
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
;
1011 con
->http_status
= 405; /* Method Not Allowed */
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
;