• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9  *
10  * This software is licensed as described in the file COPYING, which
11  * you should have received as part of this distribution. The terms
12  * are also available at https://curl.se/docs/copyright.html.
13  *
14  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15  * copies of the Software, and permit persons to whom the Software is
16  * furnished to do so, under the terms of the COPYING file.
17  *
18  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19  * KIND, either express or implied.
20  *
21  * SPDX-License-Identifier: curl
22  *
23  ***************************************************************************/
24 
25 #include "curl_setup.h"
26 
27 #include "http_proxy.h"
28 
29 #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_PROXY)
30 
31 #include <curl/curl.h>
32 #include "sendf.h"
33 #include "http.h"
34 #include "url.h"
35 #include "select.h"
36 #include "progress.h"
37 #include "cfilters.h"
38 #include "cf-h1-proxy.h"
39 #include "cf-h2-proxy.h"
40 #include "connect.h"
41 #include "curlx.h"
42 #include "vtls/vtls.h"
43 #include "transfer.h"
44 #include "multiif.h"
45 #include "vauth/vauth.h"
46 
47 /* The last 3 #include files should be in this order */
48 #include "curl_printf.h"
49 #include "curl_memory.h"
50 #include "memdebug.h"
51 
hd_name_eq(const char * n1,size_t n1len,const char * n2,size_t n2len)52 static bool hd_name_eq(const char *n1, size_t n1len,
53                        const char *n2, size_t n2len)
54 {
55   return (n1len == n2len) ? strncasecompare(n1, n2, n1len) : FALSE;
56 }
57 
dynhds_add_custom(struct Curl_easy * data,bool is_connect,int httpversion,struct dynhds * hds)58 static CURLcode dynhds_add_custom(struct Curl_easy *data,
59                                   bool is_connect, int httpversion,
60                                   struct dynhds *hds)
61 {
62   struct connectdata *conn = data->conn;
63   char *ptr;
64   struct curl_slist *h[2];
65   struct curl_slist *headers;
66   int numlists = 1; /* by default */
67   int i;
68 
69   enum Curl_proxy_use proxy;
70 
71   if(is_connect)
72     proxy = HEADER_CONNECT;
73   else
74     proxy = conn->bits.httpproxy && !conn->bits.tunnel_proxy ?
75       HEADER_PROXY : HEADER_SERVER;
76 
77   switch(proxy) {
78   case HEADER_SERVER:
79     h[0] = data->set.headers;
80     break;
81   case HEADER_PROXY:
82     h[0] = data->set.headers;
83     if(data->set.sep_headers) {
84       h[1] = data->set.proxyheaders;
85       numlists++;
86     }
87     break;
88   case HEADER_CONNECT:
89     if(data->set.sep_headers)
90       h[0] = data->set.proxyheaders;
91     else
92       h[0] = data->set.headers;
93     break;
94   }
95 
96   /* loop through one or two lists */
97   for(i = 0; i < numlists; i++) {
98     for(headers = h[i]; headers; headers = headers->next) {
99       const char *name, *value;
100       size_t namelen, valuelen;
101 
102       /* There are 2 quirks in place for custom headers:
103        * 1. setting only 'name:' to suppress a header from being sent
104        * 2. setting only 'name;' to send an empty (illegal) header
105        */
106       ptr = strchr(headers->data, ':');
107       if(ptr) {
108         name = headers->data;
109         namelen = ptr - headers->data;
110         ptr++; /* pass the colon */
111         while(*ptr && ISSPACE(*ptr))
112           ptr++;
113         if(*ptr) {
114           value = ptr;
115           valuelen = strlen(value);
116         }
117         else {
118           /* quirk #1, suppress this header */
119           continue;
120         }
121       }
122       else {
123         ptr = strchr(headers->data, ';');
124 
125         if(!ptr) {
126           /* neither : nor ; in provided header value. We seem
127            * to ignore this silently */
128           continue;
129         }
130 
131         name = headers->data;
132         namelen = ptr - headers->data;
133         ptr++; /* pass the semicolon */
134         while(*ptr && ISSPACE(*ptr))
135           ptr++;
136         if(!*ptr) {
137           /* quirk #2, send an empty header */
138           value = "";
139           valuelen = 0;
140         }
141         else {
142           /* this may be used for something else in the future,
143            * ignore this for now */
144           continue;
145         }
146       }
147 
148       DEBUGASSERT(name && value);
149       if(data->state.aptr.host &&
150          /* a Host: header was sent already, do not pass on any custom Host:
151             header as that will produce *two* in the same request! */
152          hd_name_eq(name, namelen, STRCONST("Host:")))
153         ;
154       else if(data->state.httpreq == HTTPREQ_POST_FORM &&
155               /* this header (extended by formdata.c) is sent later */
156               hd_name_eq(name, namelen, STRCONST("Content-Type:")))
157         ;
158       else if(data->state.httpreq == HTTPREQ_POST_MIME &&
159               /* this header is sent later */
160               hd_name_eq(name, namelen, STRCONST("Content-Type:")))
161         ;
162       else if(data->req.authneg &&
163               /* while doing auth neg, do not allow the custom length since
164                  we will force length zero then */
165               hd_name_eq(name, namelen, STRCONST("Content-Length:")))
166         ;
167       else if(data->state.aptr.te &&
168               /* when asking for Transfer-Encoding, do not pass on a custom
169                  Connection: */
170               hd_name_eq(name, namelen, STRCONST("Connection:")))
171         ;
172       else if((httpversion >= 20) &&
173               hd_name_eq(name, namelen, STRCONST("Transfer-Encoding:")))
174         /* HTTP/2 and HTTP/3 do not support chunked requests */
175         ;
176       else if((hd_name_eq(name, namelen, STRCONST("Authorization:")) ||
177                hd_name_eq(name, namelen, STRCONST("Cookie:"))) &&
178               /* be careful of sending this potentially sensitive header to
179                  other hosts */
180               !Curl_auth_allowed_to_host(data))
181         ;
182       else {
183         CURLcode result;
184 
185         result = Curl_dynhds_add(hds, name, namelen, value, valuelen);
186         if(result)
187           return result;
188       }
189     }
190   }
191 
192   return CURLE_OK;
193 }
194 
Curl_http_proxy_get_destination(struct Curl_cfilter * cf,const char ** phostname,int * pport,bool * pipv6_ip)195 CURLcode Curl_http_proxy_get_destination(struct Curl_cfilter *cf,
196                                          const char **phostname,
197                                          int *pport, bool *pipv6_ip)
198 {
199   DEBUGASSERT(cf);
200   DEBUGASSERT(cf->conn);
201 
202   if(cf->conn->bits.conn_to_host)
203     *phostname = cf->conn->conn_to_host.name;
204   else if(cf->sockindex == SECONDARYSOCKET)
205     *phostname = cf->conn->secondaryhostname;
206   else
207     *phostname = cf->conn->host.name;
208 
209   if(cf->sockindex == SECONDARYSOCKET)
210     *pport = cf->conn->secondary_port;
211   else if(cf->conn->bits.conn_to_port)
212     *pport = cf->conn->conn_to_port;
213   else
214     *pport = cf->conn->remote_port;
215 
216   if(*phostname != cf->conn->host.name)
217     *pipv6_ip = (strchr(*phostname, ':') != NULL);
218   else
219     *pipv6_ip = cf->conn->bits.ipv6_ip;
220 
221   return CURLE_OK;
222 }
223 
224 struct cf_proxy_ctx {
225   /* the protocol specific sub-filter we install during connect */
226   struct Curl_cfilter *cf_protocol;
227   int httpversion; /* HTTP version used to CONNECT */
228 };
229 
Curl_http_proxy_create_CONNECT(struct httpreq ** preq,struct Curl_cfilter * cf,struct Curl_easy * data,int http_version_major)230 CURLcode Curl_http_proxy_create_CONNECT(struct httpreq **preq,
231                                         struct Curl_cfilter *cf,
232                                         struct Curl_easy *data,
233                                         int http_version_major)
234 {
235   struct cf_proxy_ctx *ctx = cf->ctx;
236   const char *hostname = NULL;
237   char *authority = NULL;
238   int port;
239   bool ipv6_ip;
240   CURLcode result;
241   struct httpreq *req = NULL;
242 
243   result = Curl_http_proxy_get_destination(cf, &hostname, &port, &ipv6_ip);
244   if(result)
245     goto out;
246 
247   authority = aprintf("%s%s%s:%d", ipv6_ip ? "[" : "", hostname,
248                       ipv6_ip ?"]" : "", port);
249   if(!authority) {
250     result = CURLE_OUT_OF_MEMORY;
251     goto out;
252   }
253 
254   result = Curl_http_req_make(&req, "CONNECT", sizeof("CONNECT")-1,
255                               NULL, 0, authority, strlen(authority),
256                               NULL, 0);
257   if(result)
258     goto out;
259 
260   /* Setup the proxy-authorization header, if any */
261   result = Curl_http_output_auth(data, cf->conn, req->method, HTTPREQ_GET,
262                                  req->authority, TRUE);
263   if(result)
264     goto out;
265 
266   /* If user is not overriding Host: header, we add for HTTP/1.x */
267   if(http_version_major == 1 &&
268      !Curl_checkProxyheaders(data, cf->conn, STRCONST("Host"))) {
269     result = Curl_dynhds_cadd(&req->headers, "Host", authority);
270     if(result)
271       goto out;
272   }
273 
274   if(data->state.aptr.proxyuserpwd) {
275     result = Curl_dynhds_h1_cadd_line(&req->headers,
276                                       data->state.aptr.proxyuserpwd);
277     if(result)
278       goto out;
279   }
280 
281   if(!Curl_checkProxyheaders(data, cf->conn, STRCONST("User-Agent")) &&
282      data->set.str[STRING_USERAGENT] && *data->set.str[STRING_USERAGENT]) {
283     result = Curl_dynhds_cadd(&req->headers, "User-Agent",
284                               data->set.str[STRING_USERAGENT]);
285     if(result)
286       goto out;
287   }
288 
289   if(http_version_major == 1 &&
290     !Curl_checkProxyheaders(data, cf->conn, STRCONST("Proxy-Connection"))) {
291     result = Curl_dynhds_cadd(&req->headers, "Proxy-Connection", "Keep-Alive");
292     if(result)
293       goto out;
294   }
295 
296   result = dynhds_add_custom(data, TRUE, ctx->httpversion, &req->headers);
297 
298 out:
299   if(result && req) {
300     Curl_http_req_free(req);
301     req = NULL;
302   }
303   free(authority);
304   *preq = req;
305   return result;
306 }
307 
http_proxy_cf_connect(struct Curl_cfilter * cf,struct Curl_easy * data,bool blocking,bool * done)308 static CURLcode http_proxy_cf_connect(struct Curl_cfilter *cf,
309                                       struct Curl_easy *data,
310                                       bool blocking, bool *done)
311 {
312   struct cf_proxy_ctx *ctx = cf->ctx;
313   CURLcode result;
314 
315   if(cf->connected) {
316     *done = TRUE;
317     return CURLE_OK;
318   }
319 
320   CURL_TRC_CF(data, cf, "connect");
321 connect_sub:
322   result = cf->next->cft->do_connect(cf->next, data, blocking, done);
323   if(result || !*done)
324     return result;
325 
326   *done = FALSE;
327   if(!ctx->cf_protocol) {
328     struct Curl_cfilter *cf_protocol = NULL;
329     int httpversion = 0;
330     int alpn = Curl_conn_cf_is_ssl(cf->next) ?
331       cf->conn->proxy_alpn : CURL_HTTP_VERSION_1_1;
332 
333     /* First time call after the subchain connected */
334     switch(alpn) {
335     case CURL_HTTP_VERSION_NONE:
336     case CURL_HTTP_VERSION_1_0:
337     case CURL_HTTP_VERSION_1_1:
338       CURL_TRC_CF(data, cf, "installing subfilter for HTTP/1.1");
339       infof(data, "CONNECT tunnel: HTTP/1.%d negotiated",
340             (alpn == CURL_HTTP_VERSION_1_0) ? 0 : 1);
341       result = Curl_cf_h1_proxy_insert_after(cf, data);
342       if(result)
343         goto out;
344       cf_protocol = cf->next;
345       httpversion = (alpn == CURL_HTTP_VERSION_1_0) ? 10 : 11;
346       break;
347 #ifdef USE_NGHTTP2
348     case CURL_HTTP_VERSION_2:
349       CURL_TRC_CF(data, cf, "installing subfilter for HTTP/2");
350       infof(data, "CONNECT tunnel: HTTP/2 negotiated");
351       result = Curl_cf_h2_proxy_insert_after(cf, data);
352       if(result)
353         goto out;
354       cf_protocol = cf->next;
355       httpversion = 20;
356       break;
357 #endif
358     default:
359       infof(data, "CONNECT tunnel: unsupported ALPN(%d) negotiated", alpn);
360       result = CURLE_COULDNT_CONNECT;
361       goto out;
362     }
363 
364     ctx->cf_protocol = cf_protocol;
365     ctx->httpversion = httpversion;
366     /* after we installed the filter "below" us, we call connect
367      * on out sub-chain again.
368      */
369     goto connect_sub;
370   }
371   else {
372     /* subchain connected and we had already installed the protocol filter.
373      * This means the protocol tunnel is established, we are done.
374      */
375     DEBUGASSERT(ctx->cf_protocol);
376     result = CURLE_OK;
377   }
378 
379 out:
380   if(!result) {
381     cf->connected = TRUE;
382     *done = TRUE;
383   }
384   return result;
385 }
386 
Curl_cf_http_proxy_get_host(struct Curl_cfilter * cf,struct Curl_easy * data,const char ** phost,const char ** pdisplay_host,int * pport)387 void Curl_cf_http_proxy_get_host(struct Curl_cfilter *cf,
388                                  struct Curl_easy *data,
389                                  const char **phost,
390                                  const char **pdisplay_host,
391                                  int *pport)
392 {
393   (void)data;
394   if(!cf->connected) {
395     *phost = cf->conn->http_proxy.host.name;
396     *pdisplay_host = cf->conn->http_proxy.host.dispname;
397     *pport = (int)cf->conn->http_proxy.port;
398   }
399   else {
400     cf->next->cft->get_host(cf->next, data, phost, pdisplay_host, pport);
401   }
402 }
403 
http_proxy_cf_destroy(struct Curl_cfilter * cf,struct Curl_easy * data)404 static void http_proxy_cf_destroy(struct Curl_cfilter *cf,
405                                   struct Curl_easy *data)
406 {
407   struct cf_proxy_ctx *ctx = cf->ctx;
408 
409   (void)data;
410   CURL_TRC_CF(data, cf, "destroy");
411   free(ctx);
412 }
413 
http_proxy_cf_close(struct Curl_cfilter * cf,struct Curl_easy * data)414 static void http_proxy_cf_close(struct Curl_cfilter *cf,
415                                 struct Curl_easy *data)
416 {
417   struct cf_proxy_ctx *ctx = cf->ctx;
418 
419   CURL_TRC_CF(data, cf, "close");
420   cf->connected = FALSE;
421   if(ctx->cf_protocol) {
422     struct Curl_cfilter *f;
423     /* if someone already removed it, we assume he also
424      * took care of destroying it. */
425     for(f = cf->next; f; f = f->next) {
426       if(f == ctx->cf_protocol) {
427         /* still in our sub-chain */
428         Curl_conn_cf_discard_sub(cf, ctx->cf_protocol, data, FALSE);
429         break;
430       }
431     }
432     ctx->cf_protocol = NULL;
433   }
434   if(cf->next)
435     cf->next->cft->do_close(cf->next, data);
436 }
437 
438 
439 struct Curl_cftype Curl_cft_http_proxy = {
440   "HTTP-PROXY",
441   CF_TYPE_IP_CONNECT|CF_TYPE_PROXY,
442   0,
443   http_proxy_cf_destroy,
444   http_proxy_cf_connect,
445   http_proxy_cf_close,
446   Curl_cf_def_shutdown,
447   Curl_cf_http_proxy_get_host,
448   Curl_cf_def_adjust_pollset,
449   Curl_cf_def_data_pending,
450   Curl_cf_def_send,
451   Curl_cf_def_recv,
452   Curl_cf_def_cntrl,
453   Curl_cf_def_conn_is_alive,
454   Curl_cf_def_conn_keep_alive,
455   Curl_cf_def_query,
456 };
457 
Curl_cf_http_proxy_insert_after(struct Curl_cfilter * cf_at,struct Curl_easy * data)458 CURLcode Curl_cf_http_proxy_insert_after(struct Curl_cfilter *cf_at,
459                                          struct Curl_easy *data)
460 {
461   struct Curl_cfilter *cf;
462   struct cf_proxy_ctx *ctx = NULL;
463   CURLcode result;
464 
465   (void)data;
466   ctx = calloc(1, sizeof(*ctx));
467   if(!ctx) {
468     result = CURLE_OUT_OF_MEMORY;
469     goto out;
470   }
471   result = Curl_cf_create(&cf, &Curl_cft_http_proxy, ctx);
472   if(result)
473     goto out;
474   ctx = NULL;
475   Curl_conn_cf_insert_after(cf_at, cf);
476 
477 out:
478   free(ctx);
479   return result;
480 }
481 
482 #endif /* ! CURL_DISABLE_HTTP && !CURL_DISABLE_PROXY */
483