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