6 #include "gw_backend.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
;
31 /*(not used in plugin_config, but used in handler_ctx)*/
32 const buffer
*http_host
;
33 const buffer
*forwarded_host
;
34 const data_string
*forwarded_urlpath
;
35 } http_header_remap_opts
;
38 PROXY_FORWARDED_NONE
= 0x00,
39 PROXY_FORWARDED_FOR
= 0x01,
40 PROXY_FORWARDED_PROTO
= 0x02,
41 PROXY_FORWARDED_HOST
= 0x04,
42 PROXY_FORWARDED_BY
= 0x08,
43 PROXY_FORWARDED_REMOTE_USER
= 0x10
48 array
*forwarded_params
;
50 unsigned short replace_http_host
;
51 unsigned int forwarded
;
53 http_header_remap_opts header
;
58 plugin_config
**config_storage
;
63 static int proxy_check_extforward
;
67 http_response_opts opts
;
68 http_header_remap_opts remap_hdrs
;
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(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
];
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 if (da
->type
!= TYPE_ARRAY
|| !array_is_kvstring(da
->value
)) {
245 log_error_write(srv
, __FILE__
, __LINE__
, "sb",
246 "unexpected value for proxy.header; expected ( \"param\" => ( \"key\" => \"value\" ) ) near key", da
->key
);
247 return HANDLER_ERROR
;
249 if (buffer_is_equal_string(da
->key
, CONST_STR_LEN("map-urlpath"))) {
250 s
->header
.urlpaths
= da
->value
;
252 else if (buffer_is_equal_string(da
->key
, CONST_STR_LEN("map-host-request"))) {
253 s
->header
.hosts_request
= da
->value
;
255 else if (buffer_is_equal_string(da
->key
, CONST_STR_LEN("map-host-response"))) {
256 s
->header
.hosts_response
= da
->value
;
259 log_error_write(srv
, __FILE__
, __LINE__
, "sb",
260 "unexpected key for proxy.header; expected ( \"param\" => ( \"key\" => \"value\" ) ) near key", da
->key
);
261 return HANDLER_ERROR
;
266 for (i
= 0; i
< srv
->srvconf
.modules
->used
; i
++) {
267 data_string
*ds
= (data_string
*)srv
->srvconf
.modules
->data
[i
];
268 if (buffer_is_equal_string(ds
->value
, CONST_STR_LEN("mod_extforward"))) {
269 proxy_check_extforward
= 1;
274 return HANDLER_GO_ON
;
278 /* (future: might move to http-header-glue.c) */
279 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
)
281 const array
*hosts
= is_req
282 ? remap_hdrs
->hosts_request
283 : remap_hdrs
->hosts_response
;
285 const char * const s
= b
->ptr
+off
;
286 for (size_t i
= 0, used
= hosts
->used
; i
< used
; ++i
) {
287 const data_string
* const ds
= (data_string
*)hosts
->data
[i
];
288 const buffer
*k
= ds
->key
;
289 size_t mlen
= buffer_string_length(k
);
290 if (1 == mlen
&& k
->ptr
[0] == '-') {
291 /* match with authority provided in Host (if is_req)
292 * (If no Host in client request, then matching against empty
293 * string will probably not match, and no remap will be
296 ? remap_hdrs
->http_host
297 : remap_hdrs
->forwarded_host
;
298 if (NULL
== k
) continue;
299 mlen
= buffer_string_length(k
);
301 if (mlen
== alen
&& 0 == strncasecmp(s
, k
->ptr
, alen
)) {
302 if (buffer_is_equal_string(ds
->value
, CONST_STR_LEN("-"))) {
303 return remap_hdrs
->http_host
;
305 else if (!buffer_string_is_empty(ds
->value
)) {
306 /*(save first matched request host for response match)*/
307 if (is_req
&& NULL
== remap_hdrs
->forwarded_host
)
308 remap_hdrs
->forwarded_host
= ds
->value
;
310 } /*(else leave authority as-is and stop matching)*/
319 /* (future: might move to http-header-glue.c) */
320 static size_t http_header_remap_host (buffer
*b
, size_t off
, http_header_remap_opts
*remap_hdrs
, int is_req
, size_t alen
)
322 const buffer
* const m
=
323 http_header_remap_host_match(b
, off
, remap_hdrs
, is_req
, alen
);
324 if (NULL
== m
) return alen
; /*(no match; return original authority length)*/
326 buffer_substr_replace(b
, off
, alen
, m
);
327 return buffer_string_length(m
); /*(length of replacement authority)*/
331 /* (future: might move to http-header-glue.c) */
332 static void http_header_remap_urlpath (buffer
*b
, size_t off
, http_header_remap_opts
*remap_hdrs
, int is_req
)
334 const array
*urlpaths
= remap_hdrs
->urlpaths
;
336 const char * const s
= b
->ptr
+off
;
337 const size_t plen
= buffer_string_length(b
) - off
; /*(urlpath len)*/
338 if (is_req
) { /* request */
339 for (size_t i
= 0, used
= urlpaths
->used
; i
< used
; ++i
) {
340 const data_string
* const ds
= (data_string
*)urlpaths
->data
[i
];
341 const size_t mlen
= buffer_string_length(ds
->key
);
342 if (mlen
<= plen
&& 0 == memcmp(s
, ds
->key
->ptr
, mlen
)) {
343 if (NULL
== remap_hdrs
->forwarded_urlpath
)
344 remap_hdrs
->forwarded_urlpath
= ds
;
345 buffer_substr_replace(b
, off
, mlen
, ds
->value
);
350 else { /* response; perform reverse map */
351 if (NULL
!= remap_hdrs
->forwarded_urlpath
) {
352 const data_string
* const ds
= remap_hdrs
->forwarded_urlpath
;
353 const size_t mlen
= buffer_string_length(ds
->value
);
354 if (mlen
<= plen
&& 0 == memcmp(s
, ds
->value
->ptr
, mlen
)) {
355 buffer_substr_replace(b
, off
, mlen
, ds
->key
);
359 for (size_t i
= 0, used
= urlpaths
->used
; i
< used
; ++i
) {
360 const data_string
* const ds
= (data_string
*)urlpaths
->data
[i
];
361 const size_t mlen
= buffer_string_length(ds
->value
);
362 if (mlen
<= plen
&& 0 == memcmp(s
, ds
->value
->ptr
, mlen
)) {
363 buffer_substr_replace(b
, off
, mlen
, ds
->key
);
372 /* (future: might move to http-header-glue.c) */
373 static void http_header_remap_uri (buffer
*b
, size_t off
, http_header_remap_opts
*remap_hdrs
, int is_req
)
375 /* find beginning of URL-path (might be preceded by scheme://authority
376 * (caller should make sure any leading whitespace is prior to offset) */
377 if (b
->ptr
[off
] != '/') {
378 char *s
= b
->ptr
+off
;
379 size_t alen
; /*(authority len (host len))*/
380 size_t slen
; /*(scheme len)*/
382 /* skip over scheme and authority of URI to find beginning of URL-path
383 * (value might conceivably be relative URL-path instead of URI) */
384 if (NULL
== (s
= strchr(s
, ':')) || s
[1] != '/' || s
[2] != '/') return;
385 slen
= s
- (b
->ptr
+off
);
387 off
= (size_t)(s
- b
->ptr
);
388 if (NULL
!= (s
= strchr(s
, '/'))) {
389 alen
= (size_t)(s
- b
->ptr
) - off
;
390 if (0 == alen
) return; /*(empty authority, e.g. "http:///")*/
393 alen
= buffer_string_length(b
) - off
;
394 if (0 == alen
) return; /*(empty authority, e.g. "http:///")*/
395 buffer_append_string_len(b
, CONST_STR_LEN("/"));
398 /* remap authority (if configured) and set offset to url-path */
399 m
= http_header_remap_host_match(b
, off
, remap_hdrs
, is_req
, alen
);
401 if (remap_hdrs
->https_remap
402 && (is_req
? 5==slen
&& 0==memcmp(b
->ptr
+off
-slen
-3,"https",5)
403 : 4==slen
&& 0==memcmp(b
->ptr
+off
-slen
-3,"http",4))){
405 memcpy(b
->ptr
+off
-slen
-3+4,"://",3); /*("https"=>"http")*/
410 memcpy(b
->ptr
+off
-slen
-3+4,"s://",4); /*("http" =>"https")*/
415 buffer_substr_replace(b
, off
, alen
, m
);
416 alen
= buffer_string_length(m
);/*(length of replacement authority)*/
421 /* remap URLs (if configured) */
422 http_header_remap_urlpath(b
, off
, remap_hdrs
, is_req
);
426 /* (future: might move to http-header-glue.c) */
427 static void http_header_remap_setcookie (buffer
*b
, size_t off
, http_header_remap_opts
*remap_hdrs
)
429 /* Given the special-case of Set-Cookie and the (too) loosely restricted
430 * characters allowed, for best results, the Set-Cookie value should be the
431 * entire string in b from offset to end of string. In response headers,
432 * lighttpd may concatenate multiple Set-Cookie headers into single entry
433 * in con->response.headers, separated by "\r\nSet-Cookie: " */
434 for (char *s
, *n
= b
->ptr
+off
; (s
= n
); ) {
438 len
= (size_t)(b
->ptr
+ buffer_string_length(b
) - s
);
441 len
= (size_t)(n
- s
);
442 n
+= sizeof("Set-Cookie: "); /*(include +1 for '\n')*/
444 for (char *e
= s
; NULL
!= (s
= memchr(e
, ';', len
)); ) {
445 do { ++s
; } while (*s
== ' ' || *s
== '\t');
446 if ('\0' == *s
) return;
447 /*(interested only in Domain and Path attributes)*/
448 e
= memchr(s
, '=', len
- (size_t)(s
- e
));
449 if (NULL
== e
) { e
= s
+1; continue; }
451 switch ((int)(e
- s
- 1)) {
453 if (0 == strncasecmp(s
, "path", 4)) {
455 if (*e
!= '/') continue;
456 off
= (size_t)(e
- b
->ptr
);
457 http_header_remap_urlpath(b
, off
, remap_hdrs
, 0);
458 e
= b
->ptr
+off
; /*(b may have been reallocated)*/
463 if (0 == strncasecmp(s
, "domain", 6)) {
467 if (*e
== ';') continue;
468 off
= (size_t)(e
- b
->ptr
);
469 for (char c
; (c
= e
[alen
]) != ';' && c
!= ' ' && c
!= '\t'
470 && c
!= '\r' && c
!= '\0'; ++alen
);
471 len
= http_header_remap_host(b
, off
, remap_hdrs
, 0, alen
);
472 e
= b
->ptr
+off
+len
; /*(b may have been reallocated)*/
484 static void proxy_append_header(connection
*con
, const char *key
, const size_t klen
, const char *value
, const size_t vlen
) {
487 if (NULL
== (ds_dst
= (data_string
*)array_get_unused_element(con
->request
.headers
, TYPE_STRING
))) {
488 ds_dst
= data_string_init();
491 buffer_copy_string_len(ds_dst
->key
, key
, klen
);
492 buffer_copy_string_len(ds_dst
->value
, value
, vlen
);
493 array_insert_unique(con
->request
.headers
, (data_unset
*)ds_dst
);
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 data_string
*ds
= NULL
, *dsfor
= NULL
, *dsproto
= NULL
, *dshost
= NULL
;
519 if (proxy_check_extforward
) {
520 dsfor
= (data_string
*)
521 array_get_element(con
->environment
, "_L_EXTFORWARD_ACTUAL_FOR");
522 dsproto
= (data_string
*)
523 array_get_element(con
->environment
, "_L_EXTFORWARD_ACTUAL_PROTO");
524 dshost
= (data_string
*)
525 array_get_element(con
->environment
, "_L_EXTFORWARD_ACTUAL_HOST");
528 /* note: set "Forwarded" prior to updating X-Forwarded-For (below) */
532 array_get_element(con
->request
.headers
, "Forwarded");
534 if (flags
&& NULL
== ds
) {
537 array_get_unused_element(con
->request
.headers
, TYPE_STRING
);
538 if (NULL
== ds
) ds
= data_string_init();
539 buffer_copy_string_len(ds
->key
, CONST_STR_LEN("Forwarded"));
540 array_insert_unique(con
->request
.headers
, (data_unset
*)ds
);
541 xff
= (data_string
*)
542 array_get_element(con
->request
.headers
, "X-Forwarded-For");
543 if (NULL
!= xff
&& !buffer_string_is_empty(xff
->value
)) {
544 /* use X-Forwarded-For contents to seed Forwarded */
545 char *s
= xff
->value
->ptr
;
546 size_t used
= buffer_string_length(xff
->value
);
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(ds
->value
, 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(ds
->value
, CONST_STR_LEN("\""));
562 buffer_append_string_len(ds
->value
, CONST_STR_LEN("["));
563 buffer_append_string_backslash_escaped(ds
->value
, s
+j
, i
-j
);
565 buffer_append_string_len(ds
->value
, CONST_STR_LEN("]"));
566 buffer_append_string_len(ds
->value
, CONST_STR_LEN("\""));
567 buffer_append_string_len(ds
->value
, CONST_STR_LEN(", "));
570 } else if (flags
) { /*(NULL != ds)*/
571 buffer_append_string_len(ds
->value
, CONST_STR_LEN(", "));
574 if (flags
& PROXY_FORWARDED_FOR
) {
575 int family
= sock_addr_get_family(&con
->dst_addr
);
576 buffer_append_string_len(ds
->value
, 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(dsfor
->value
->ptr
, ':'));
585 buffer_append_string_len(ds
->value
, CONST_STR_LEN("\""));
586 if (ipv6
) buffer_append_string_len(ds
->value
, CONST_STR_LEN("["));
587 buffer_append_string_backslash_escaped(
588 ds
->value
, CONST_BUF_LEN(dsfor
->value
));
589 if (ipv6
) buffer_append_string_len(ds
->value
, CONST_STR_LEN("]"));
590 buffer_append_string_len(ds
->value
, 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(ds
->value
, con
->dst_addr_buf
);
595 } else if (family
== AF_INET6
) {
596 buffer_append_string_len(ds
->value
, CONST_STR_LEN("\"["));
597 buffer_append_string_buffer(ds
->value
, con
->dst_addr_buf
);
598 buffer_append_string_len(ds
->value
, CONST_STR_LEN("]\""));
600 buffer_append_string_len(ds
->value
, CONST_STR_LEN("\""));
601 buffer_append_string_backslash_escaped(
602 ds
->value
, CONST_BUF_LEN(con
->dst_addr_buf
));
603 buffer_append_string_len(ds
->value
, 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(ds
->value
, CONST_STR_LEN(";"));
617 buffer_append_string_len(ds
->value
, CONST_STR_LEN("by="));
618 buffer_append_string_len(ds
->value
, 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 ds
->value
, 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(ds
->value
, &addr
);
634 buffer_append_string_len(ds
->value
, 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(ds
->value
, CONST_STR_LEN(";"));
642 buffer_append_string_len(ds
->value
, CONST_STR_LEN("proto="));
643 if (NULL
!= dsproto
) {
644 buffer_append_string_buffer(ds
->value
, dsproto
->value
);
645 } else if (con
->srv_socket
->is_ssl
) {
646 buffer_append_string_len(ds
->value
, CONST_STR_LEN("https"));
648 buffer_append_string_len(ds
->value
, CONST_STR_LEN("http"));
653 if (flags
& PROXY_FORWARDED_HOST
) {
654 if (NULL
!= dshost
) {
656 buffer_append_string_len(ds
->value
, CONST_STR_LEN(";"));
657 buffer_append_string_len(ds
->value
, CONST_STR_LEN("host=\""));
658 buffer_append_string_backslash_escaped(
659 ds
->value
, CONST_BUF_LEN(dshost
->value
));
660 buffer_append_string_len(ds
->value
, CONST_STR_LEN("\""));
662 } else if (!buffer_string_is_empty(con
->request
.http_host
)) {
664 buffer_append_string_len(ds
->value
, CONST_STR_LEN(";"));
665 buffer_append_string_len(ds
->value
, CONST_STR_LEN("host=\""));
666 buffer_append_string_backslash_escaped(
667 ds
->value
, CONST_BUF_LEN(con
->request
.http_host
));
668 buffer_append_string_len(ds
->value
, CONST_STR_LEN("\""));
673 if (flags
& PROXY_FORWARDED_REMOTE_USER
) {
674 data_string
*remote_user
= (data_string
*)
675 array_get_element(con
->environment
, "REMOTE_USER");
676 if (NULL
!= remote_user
) {
678 buffer_append_string_len(ds
->value
, CONST_STR_LEN(";"));
679 buffer_append_string_len(ds
->value
,CONST_STR_LEN("remote_user=\""));
680 buffer_append_string_backslash_escaped(
681 ds
->value
, CONST_BUF_LEN(remote_user
->value
));
682 buffer_append_string_len(ds
->value
,CONST_STR_LEN("\""));
687 /* legacy X-* headers, including X-Forwarded-For */
689 b
= (NULL
!= dsfor
) ? dsfor
->value
: con
->dst_addr_buf
;
690 proxy_append_header(con
, CONST_STR_LEN("X-Forwarded-For"),
693 b
= (NULL
!= dshost
) ? dshost
->value
: con
->request
.http_host
;
694 if (!buffer_string_is_empty(b
)) {
695 proxy_append_header(con
, CONST_STR_LEN("X-Host"),
697 proxy_append_header(con
, CONST_STR_LEN("X-Forwarded-Host"),
701 b
= (NULL
!= dsproto
) ? dsproto
->value
: con
->uri
.scheme
;
702 proxy_append_header(con
, CONST_STR_LEN("X-Forwarded-Proto"),
707 static handler_t
proxy_create_env(server
*srv
, gw_handler_ctx
*gwhctx
) {
708 handler_ctx
*hctx
= (handler_ctx
*)gwhctx
;
709 connection
*con
= hctx
->gw
.remote_conn
;
710 buffer
*b
= buffer_init();
711 const int remap_headers
= (NULL
!= hctx
->remap_hdrs
.urlpaths
712 || NULL
!= hctx
->remap_hdrs
.hosts_request
);
713 const int upgrade
= hctx
->remap_hdrs
.upgrade
714 && (NULL
!= array_get_element(con
->request
.headers
, "Upgrade"));
715 buffer_string_prepare_copy(b
, 8192-1);
720 buffer_copy_string(b
, get_http_method_name(con
->request
.http_method
));
721 buffer_append_string_len(b
, CONST_STR_LEN(" "));
722 buffer_append_string_buffer(b
, con
->request
.uri
);
724 http_header_remap_uri(b
, buffer_string_length(b
) - buffer_string_length(con
->request
.uri
), &hctx
->remap_hdrs
, 1);
726 buffer_append_string_len(b
, CONST_STR_LEN(" HTTP/1.0\r\n"));
728 buffer_append_string_len(b
, CONST_STR_LEN(" HTTP/1.1\r\n"));
730 if (hctx
->conf
.replace_http_host
&& !buffer_string_is_empty(hctx
->gw
.host
->id
)) {
731 if (hctx
->gw
.conf
.debug
> 1) {
732 log_error_write(srv
, __FILE__
, __LINE__
, "SBS",
733 "proxy - using \"", hctx
->gw
.host
->id
, "\" as HTTP Host");
735 buffer_append_string_len(b
, CONST_STR_LEN("Host: "));
736 buffer_append_string_buffer(b
, hctx
->gw
.host
->id
);
737 buffer_append_string_len(b
, CONST_STR_LEN("\r\n"));
738 } else if (!buffer_string_is_empty(con
->request
.http_host
)) {
739 buffer_append_string_len(b
, CONST_STR_LEN("Host: "));
740 buffer_append_string_buffer(b
, con
->request
.http_host
);
742 size_t alen
= buffer_string_length(con
->request
.http_host
);
743 http_header_remap_host(b
, buffer_string_length(b
) - alen
, &hctx
->remap_hdrs
, 1, alen
);
745 buffer_append_string_len(b
, CONST_STR_LEN("\r\n"));
748 /* "Forwarded" and legacy X- headers */
749 proxy_set_Forwarded(con
, hctx
->conf
.forwarded
);
751 if (HTTP_METHOD_GET
!= con
->request
.http_method
752 && HTTP_METHOD_HEAD
!= con
->request
.http_method
753 && con
->request
.content_length
>= 0) {
754 /* set Content-Length if client sent Transfer-Encoding: chunked
755 * and not streaming to backend (request body has been fully received) */
756 data_string
*ds
= (data_string
*) array_get_element(con
->request
.headers
, "Content-Length");
757 if (NULL
== ds
|| buffer_string_is_empty(ds
->value
)) {
758 char buf
[LI_ITOSTRING_LENGTH
];
759 li_itostrn(buf
, sizeof(buf
), con
->request
.content_length
);
761 proxy_append_header(con
, CONST_STR_LEN("Content-Length"), buf
, strlen(buf
));
763 buffer_copy_string(ds
->value
, buf
);
769 for (size_t i
= 0, used
= con
->request
.headers
->used
; i
< used
; ++i
) {
770 data_string
*ds
= (data_string
*)con
->request
.headers
->data
[i
];
771 const size_t klen
= buffer_string_length(ds
->key
);
777 if (buffer_is_equal_caseless_string(ds
->key
, CONST_STR_LEN("Host"))) continue; /*(handled further above)*/
780 if (buffer_is_equal_caseless_string(ds
->key
, CONST_STR_LEN("Connection"))) continue;
781 if (buffer_is_equal_caseless_string(ds
->key
, CONST_STR_LEN("Set-Cookie"))) continue; /*(response header only; avoid accidental reflection)*/
784 if (buffer_is_equal_caseless_string(ds
->key
, CONST_STR_LEN("Proxy-Connection"))) continue;
787 /* Do not emit HTTP_PROXY in environment.
788 * Some executables use HTTP_PROXY to configure
789 * outgoing proxy. See also https://httpoxy.org/ */
790 if (buffer_is_equal_caseless_string(ds
->key
, CONST_STR_LEN("Proxy"))) continue;
796 vlen
= buffer_string_length(ds
->value
);
797 if (0 == vlen
) continue;
799 buffer_append_string_len(b
, ds
->key
->ptr
, klen
);
800 buffer_append_string_len(b
, CONST_STR_LEN(": "));
801 buffer_append_string_len(b
, ds
->value
->ptr
, vlen
);
802 buffer_append_string_len(b
, CONST_STR_LEN("\r\n"));
804 if (!remap_headers
) continue;
806 /* check for hdrs for which to remap URIs in-place after append to b */
811 #if 0 /* "URI" is HTTP response header (non-standard; historical in Apache) */
813 if (buffer_is_equal_caseless_string(ds
->key
, CONST_STR_LEN("URI"))) break;
816 #if 0 /* "Location" is HTTP response header */
818 if (buffer_is_equal_caseless_string(ds
->key
, CONST_STR_LEN("Location"))) break;
821 case 11: /* "Destination" is WebDAV request header */
822 if (buffer_is_equal_caseless_string(ds
->key
, CONST_STR_LEN("Destination"))) break;
824 case 16: /* "Content-Location" may be HTTP request or response header */
825 if (buffer_is_equal_caseless_string(ds
->key
, CONST_STR_LEN("Content-Location"))) break;
829 http_header_remap_uri(b
, buffer_string_length(b
) - vlen
- 2, &hctx
->remap_hdrs
, 1);
833 buffer_append_string_len(b
, CONST_STR_LEN("Connection: close\r\n\r\n"));
835 buffer_append_string_len(b
, CONST_STR_LEN("Connection: close, upgrade\r\n\r\n"));
837 hctx
->gw
.wb_reqlen
= buffer_string_length(b
);
838 chunkqueue_append_buffer(hctx
->gw
.wb
, b
);
841 if (con
->request
.content_length
) {
842 chunkqueue_append_chunkqueue(hctx
->gw
.wb
, con
->request_content_queue
);
843 if (con
->request
.content_length
> 0)
844 hctx
->gw
.wb_reqlen
+= con
->request
.content_length
; /* total req size */
845 else /* as-yet-unknown total request size (Transfer-Encoding: chunked)*/
846 hctx
->gw
.wb_reqlen
= -hctx
->gw
.wb_reqlen
;
849 status_counter_inc(srv
, CONST_STR_LEN("proxy.requests"));
850 return HANDLER_GO_ON
;
855 #define PATCH_GW(x) \
856 p->conf.gw.x = s->gw.x;
857 static int mod_proxy_patch_connection(server
*srv
, connection
*con
, plugin_data
*p
) {
859 plugin_config
*s
= p
->config_storage
[0];
865 PATCH_GW(ext_mapping
);
867 PATCH(replace_http_host
);
869 PATCH(header
); /*(copies struct)*/
871 /* skip the first, the global context */
872 for (i
= 1; i
< srv
->config_context
->used
; i
++) {
873 data_config
*dc
= (data_config
*)srv
->config_context
->data
[i
];
874 s
= p
->config_storage
[i
];
876 /* condition didn't match */
877 if (!config_check_cond(srv
, con
, dc
)) continue;
880 for (j
= 0; j
< dc
->value
->used
; j
++) {
881 data_unset
*du
= dc
->value
->data
[j
];
883 if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("proxy.server"))) {
887 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("proxy.debug"))) {
889 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("proxy.balance"))) {
891 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("proxy.map-extensions"))) {
892 PATCH_GW(ext_mapping
);
893 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("proxy.replace-http-host"))) {
894 PATCH(replace_http_host
);
895 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("proxy.forwarded"))) {
897 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("proxy.header"))) {
898 PATCH(header
); /*(copies struct)*/
908 static handler_t
proxy_response_headers(server
*srv
, connection
*con
, struct http_response_opts_t
*opts
) {
909 /* response headers just completed */
910 handler_ctx
*hctx
= (handler_ctx
*)opts
->pdata
;
912 if (con
->parsed_response
& HTTP_UPGRADE
) {
913 if (hctx
->remap_hdrs
.upgrade
&& con
->http_status
== 101) {
914 /* 101 Switching Protocols; transition to transparent proxy */
915 gw_set_transparent(srv
, &hctx
->gw
);
916 http_response_upgrade_read_body_unknown(srv
, con
);
919 con
->parsed_response
&= ~HTTP_UPGRADE
;
921 /* preserve prior questionable behavior; likely broken behavior
922 * anyway if backend thinks connection is being upgraded but client
923 * does not receive Connection: upgrade */
924 response_header_overwrite(srv
, con
, CONST_STR_LEN("Upgrade"),
930 /* rewrite paths, if needed */
932 if (NULL
== hctx
->remap_hdrs
.urlpaths
933 && NULL
== hctx
->remap_hdrs
.hosts_response
)
934 return HANDLER_GO_ON
;
936 if (con
->parsed_response
& HTTP_LOCATION
) {
937 data_string
*ds
= (data_string
*)
938 array_get_element(con
->response
.headers
, "Location");
939 if (ds
) http_header_remap_uri(ds
->value
, 0, &hctx
->remap_hdrs
, 0);
941 if (con
->parsed_response
& HTTP_CONTENT_LOCATION
) {
942 data_string
*ds
= (data_string
*)
943 array_get_element(con
->response
.headers
, "Content-Location");
944 if (ds
) http_header_remap_uri(ds
->value
, 0, &hctx
->remap_hdrs
, 0);
946 if (con
->parsed_response
& HTTP_SET_COOKIE
) {
947 data_string
*ds
= (data_string
*)
948 array_get_element(con
->response
.headers
, "Set-Cookie");
949 if (ds
) http_header_remap_setcookie(ds
->value
, 0, &hctx
->remap_hdrs
);
952 return HANDLER_GO_ON
;
955 static handler_t
mod_proxy_check_extension(server
*srv
, connection
*con
, void *p_d
) {
956 plugin_data
*p
= p_d
;
959 if (con
->mode
!= DIRECT
) return HANDLER_GO_ON
;
961 mod_proxy_patch_connection(srv
, con
, p
);
962 if (NULL
== p
->conf
.gw
.exts
) return HANDLER_GO_ON
;
964 rc
= gw_check_extension(srv
, con
, (gw_plugin_data
*)p
, 1, sizeof(handler_ctx
));
965 if (HANDLER_GO_ON
!= rc
) return rc
;
967 if (con
->mode
== p
->id
) {
968 handler_ctx
*hctx
= con
->plugin_ctx
[p
->id
];
969 hctx
->gw
.create_env
= proxy_create_env
;
970 hctx
->gw
.response
= buffer_init();
971 hctx
->gw
.opts
.backend
= BACKEND_PROXY
;
972 hctx
->gw
.opts
.pdata
= hctx
;
973 hctx
->gw
.opts
.headers
= proxy_response_headers
;
975 hctx
->remap_hdrs
= p
->conf
.header
; /*(copies struct)*/
976 hctx
->remap_hdrs
.http_host
= con
->request
.http_host
;
977 hctx
->remap_hdrs
.upgrade
&= (con
->request
.http_version
== HTTP_VERSION_1_1
);
978 /* mod_proxy currently sends all backend requests as http.
979 * https-remap is a flag since it might not be needed if backend
980 * honors Forwarded or X-Forwarded-Proto headers, e.g. by using
981 * lighttpd mod_extforward or similar functionality in backend*/
982 if (hctx
->remap_hdrs
.https_remap
) {
983 hctx
->remap_hdrs
.https_remap
=
984 buffer_is_equal_string(con
->uri
.scheme
, CONST_STR_LEN("https"));
988 return HANDLER_GO_ON
;
992 int mod_proxy_plugin_init(plugin
*p
);
993 int mod_proxy_plugin_init(plugin
*p
) {
994 p
->version
= LIGHTTPD_VERSION_ID
;
995 p
->name
= buffer_init_string("proxy");
997 p
->init
= mod_proxy_init
;
998 p
->cleanup
= mod_proxy_free
;
999 p
->set_defaults
= mod_proxy_set_defaults
;
1000 p
->connection_reset
= gw_connection_reset
;
1001 p
->handle_uri_clean
= mod_proxy_check_extension
;
1002 p
->handle_subrequest
= gw_handle_subrequest
;
1003 p
->handle_trigger
= gw_handle_trigger
;
1004 p
->handle_waitpid
= gw_handle_waitpid_cb
;