xref: /lighttpd1.4/src/mod_proxy.c (revision 15cdc313)
1 #include "first.h"
2 
3 #include <string.h>
4 #include <stdlib.h>
5 
6 #include "gw_backend.h"
7 #include "base.h"
8 #include "array.h"
9 #include "buffer.h"
10 #include "http_kv.h"
11 #include "http_header.h"
12 #include "log.h"
13 #include "sock_addr.h"
14 #include "status_counter.h"
15 
16 /**
17  *
18  * HTTP reverse proxy
19  *
20  * TODO:      - HTTP/1.1
21  *            - HTTP/1.1 persistent connection with upstream servers
22  */
23 
24 /* (future: might split struct and move part to http-header-glue.c) */
25 typedef struct http_header_remap_opts {
26     const array *urlpaths;
27     const array *hosts_request;
28     const array *hosts_response;
29     int 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;
37 
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;
46 
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;
53 
54 	http_header_remap_opts header;
55 } plugin_config;
56 
57 typedef struct {
58 	PLUGIN_DATA;
59 	plugin_config **config_storage;
60 
61 	plugin_config conf;
62 } plugin_data;
63 
64 static int proxy_check_extforward;
65 
66 typedef struct {
67 	gw_handler_ctx gw;
68 	http_response_opts opts;
69 	plugin_config conf;
70 } handler_ctx;
71 
72 
73 INIT_FUNC(mod_proxy_init) {
74 	plugin_data *p;
75 
76 	p = calloc(1, sizeof(*p));
77 
78 	return p;
79 }
80 
81 
82 FREE_FUNC(mod_proxy_free) {
83 	plugin_data *p = p_d;
84 
85 	UNUSED(srv);
86 
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];
91 
92 			if (NULL == s) continue;
93 
94 			array_free(s->forwarded_params);
95 			array_free(s->header_params);
96 
97 			/*assert(0 == offsetof(s->gw));*/
98 			gw_plugin_config_free(&s->gw);
99 			/*free(s);*//*free'd by gw_plugin_config_free()*/
100 		}
101 		free(p->config_storage);
102 	}
103 
104 	free(p);
105 
106 	return HANDLER_GO_ON;
107 }
108 
109 SETDEFAULTS_FUNC(mod_proxy_set_defaults) {
110 	plugin_data *p = p_d;
111 	data_unset *du;
112 	size_t i = 0;
113 
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 }
123 	};
124 
125 	p->config_storage = calloc(srv->config_context->used, sizeof(plugin_config *));
126 
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;
130 
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();
138 
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;
146 
147 		p->config_storage[i] = s;
148 
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;
151 		}
152 
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;
156 		}
157 
158 		du = array_get_element(config->value, "proxy.balance");
159 		if (!gw_set_defaults_balance(srv, &s->gw, du)) {
160 			return HANDLER_ERROR;
161 		}
162 
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;
169 				}
170 			}
171 		}
172 
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;
177 		}
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;
195 			}
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;
204 				}
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;
212 			}
213 		}
214 
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;
219 		}
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;
228 				}
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;
232 			}
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;
239 				}
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;
243 			}
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;
250 				}
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;
254 			}
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;
259 			}
260 			if (buffer_is_equal_string(da->key, CONST_STR_LEN("map-urlpath"))) {
261 				s->header.urlpaths = da->value;
262 			}
263 			else if (buffer_is_equal_string(da->key, CONST_STR_LEN("map-host-request"))) {
264 				s->header.hosts_request = da->value;
265 			}
266 			else if (buffer_is_equal_string(da->key, CONST_STR_LEN("map-host-response"))) {
267 				s->header.hosts_response = da->value;
268 			}
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;
273 			}
274 		}
275 	}
276 
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;
282 		}
283 	}
284 
285 	return HANDLER_GO_ON;
286 }
287 
288 
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)
291 {
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);
311             }
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;
315                 }
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;
323             }
324         }
325     }
326     return NULL;
327 }
328 
329 
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)
332 {
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)*/
336 
337     buffer_substr_replace(b, off, alen, m);
338     return buffer_string_length(m); /*(length of replacement authority)*/
339 }
340 
341 
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)
344 {
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)*/
358                 }
359             }
360         }
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)*/
368                 }
369             }
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)*/
376                 }
377             }
378         }
379     }
380     return 0;
381 }
382 
383 
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)
386 {
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:///")*/
403         }
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("/"));
408         }
409 
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;
420                 }
421                 else {/*(!is_req)*/
422                     memcpy(b->ptr+off-slen-3+4,"s://",4); /*("http" =>"https")*/
423                     ++off;
424                     --alen;
425                 }
426             }
427             buffer_substr_replace(b, off, alen, m);
428             alen = buffer_string_length(m);/*(length of replacement authority)*/
429         }
430         off += alen;
431     }
432 
433     /* remap URLs (if configured) */
434     http_header_remap_urlpath(b, off, remap_hdrs, is_req);
435 }
436 
437 
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)
440 {
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;
448         {
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:");
453             }
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 (buffer_eq_icase_ssn(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;
472                 }
473                 break;
474               case 6:
475                 if (buffer_eq_icase_ssn(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;
486                 }
487                 break;
488               default:
489                 break;
490             }
491         }
492     }
493 }
494 
495 
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;
500 
501     buffer_string_prepare_append(b, len*2 + 4);
502     p = b->ptr + buffer_string_length(b);
503 
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;
509     }
510 
511     buffer_commit(b, j);
512 }
513 
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;
517 
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"));
525     }
526 
527     /* note: set "Forwarded" prior to updating X-Forwarded-For (below) */
528 
529     if (flags)
530         b = http_header_request_get(con, HTTP_HEADER_FORWARDED, CONST_STR_LEN("Forwarded"));
531 
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(", "));
568             }
569         }
570     } else if (flags) { /*(NULL != b)*/
571         buffer_append_string_len(b, CONST_STR_LEN(", "));
572     }
573 
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("\""));
604         }
605         semicolon = 1;
606     }
607 
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 */
615 
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));
624         }
625         else
626       #endif
627         {
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);
632             }
633         }
634         buffer_append_string_len(b, CONST_STR_LEN("\""));
635         semicolon = 1;
636     }
637 
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"));
649         }
650         semicolon = 1;
651     }
652 
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;
670         }
671     }
672 
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;
684         }
685     }
686 
687     /* legacy X-* headers, including X-Forwarded-For */
688 
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));
693 
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));
702     }
703 
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));
708 }
709 
710 
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);
720 
721 	/* build header */
722 
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"));
733 
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");
738 		}
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);
748 		}
749 		buffer_append_string_len(b, CONST_STR_LEN("\r\n"));
750 	}
751 
752 	/* "Forwarded" and legacy X- headers */
753 	proxy_set_Forwarded(con, hctx->conf.forwarded);
754 
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));
766 		}
767 	}
768 
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;
795 		}
796 
797 		vlen = buffer_string_length(ds->value);
798 		if (0 == vlen) continue;
799 
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);
804 		}
805 
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"));
810 
811 		if (!remap_headers) continue;
812 
813 		/* check for hdrs for which to remap URIs in-place after append to b */
814 
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;
834 		}
835 
836 		http_header_remap_uri(b, buffer_string_length(b) - vlen - 2, &hctx->conf.header, 1);
837 	}
838 
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"));
843 
844 	hctx->gw.wb_reqlen = buffer_string_length(b);
845 	chunkqueue_prepend_buffer_commit(hctx->gw.wb);
846 
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;
853 	}
854 
855 	status_counter_inc(srv, CONST_STR_LEN("proxy.requests"));
856 	return HANDLER_GO_ON;
857 }
858 
859 
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);
867 
868 	status_counter_inc(srv, CONST_STR_LEN("proxy.requests"));
869 	return HANDLER_GO_ON;
870 }
871 
872 
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];
880 
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)*/
890 
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];
895 
896 		/* condition didn't match */
897 		if (!config_check_cond(srv, con, dc)) continue;
898 
899 		/* merge config */
900 		for (j = 0; j < dc->value->used; j++) {
901 			data_unset *du = dc->value->data[j];
902 
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)*/
919 			}
920 		}
921 	}
922 
923 	return 0;
924 }
925 #undef PATCH_GW
926 #undef PATCH
927 
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;
931 
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);
937         }
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
947         }
948     }
949 
950     /* rewrite paths, if needed */
951 
952     if (NULL == hctx->conf.header.urlpaths
953         && NULL == hctx->conf.header.hosts_response)
954         return HANDLER_GO_ON;
955 
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);
959     }
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);
963     }
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);
967     }
968 
969     return HANDLER_GO_ON;
970 }
971 
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;
975 
976 	if (con->mode != DIRECT) return HANDLER_GO_ON;
977 
978 	mod_proxy_patch_connection(srv, con, p);
979 	if (NULL == p->conf.gw.exts) return HANDLER_GO_ON;
980 
981 	rc = gw_check_extension(srv, con, (gw_plugin_data *)p, 1, sizeof(handler_ctx));
982 	if (HANDLER_GO_ON != rc) return rc;
983 
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;
991 
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"));
1002 		}
1003 
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;
1009 			}
1010 			else {
1011 				con->http_status = 405; /* Method Not Allowed */
1012 				con->mode = DIRECT;
1013 				return HANDLER_FINISHED;
1014 			}
1015 		}
1016 	}
1017 
1018 	return HANDLER_GO_ON;
1019 }
1020 
1021 
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");
1026 
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;
1035 
1036 	p->data         = NULL;
1037 
1038 	return 0;
1039 }
1040