xref: /curl/lib/http_proxy.c (revision fc3e1cbc)
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,struct dynhds * hds)58 static CURLcode dynhds_add_custom(struct Curl_easy *data,
59                                   bool is_connect,
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((conn->httpversion >= 20) &&
173               hd_name_eq(name, namelen, STRCONST("Transfer-Encoding:")))
174         /* HTTP/2 does 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 
Curl_http_proxy_create_CONNECT(struct httpreq ** preq,struct Curl_cfilter * cf,struct Curl_easy * data,int http_version_major)224 CURLcode Curl_http_proxy_create_CONNECT(struct httpreq **preq,
225                                         struct Curl_cfilter *cf,
226                                         struct Curl_easy *data,
227                                         int http_version_major)
228 {
229   const char *hostname = NULL;
230   char *authority = NULL;
231   int port;
232   bool ipv6_ip;
233   CURLcode result;
234   struct httpreq *req = NULL;
235 
236   result = Curl_http_proxy_get_destination(cf, &hostname, &port, &ipv6_ip);
237   if(result)
238     goto out;
239 
240   authority = aprintf("%s%s%s:%d", ipv6_ip ? "[" : "", hostname,
241                       ipv6_ip ?"]" : "", port);
242   if(!authority) {
243     result = CURLE_OUT_OF_MEMORY;
244     goto out;
245   }
246 
247   result = Curl_http_req_make(&req, "CONNECT", sizeof("CONNECT")-1,
248                               NULL, 0, authority, strlen(authority),
249                               NULL, 0);
250   if(result)
251     goto out;
252 
253   /* Setup the proxy-authorization header, if any */
254   result = Curl_http_output_auth(data, cf->conn, req->method, HTTPREQ_GET,
255                                  req->authority, TRUE);
256   if(result)
257     goto out;
258 
259   /* If user is not overriding Host: header, we add for HTTP/1.x */
260   if(http_version_major == 1 &&
261      !Curl_checkProxyheaders(data, cf->conn, STRCONST("Host"))) {
262     result = Curl_dynhds_cadd(&req->headers, "Host", authority);
263     if(result)
264       goto out;
265   }
266 
267   if(data->state.aptr.proxyuserpwd) {
268     result = Curl_dynhds_h1_cadd_line(&req->headers,
269                                       data->state.aptr.proxyuserpwd);
270     if(result)
271       goto out;
272   }
273 
274   if(!Curl_checkProxyheaders(data, cf->conn, STRCONST("User-Agent")) &&
275      data->set.str[STRING_USERAGENT] && *data->set.str[STRING_USERAGENT]) {
276     result = Curl_dynhds_cadd(&req->headers, "User-Agent",
277                               data->set.str[STRING_USERAGENT]);
278     if(result)
279       goto out;
280   }
281 
282   if(http_version_major == 1 &&
283     !Curl_checkProxyheaders(data, cf->conn, STRCONST("Proxy-Connection"))) {
284     result = Curl_dynhds_cadd(&req->headers, "Proxy-Connection", "Keep-Alive");
285     if(result)
286       goto out;
287   }
288 
289   result = dynhds_add_custom(data, TRUE, &req->headers);
290 
291 out:
292   if(result && req) {
293     Curl_http_req_free(req);
294     req = NULL;
295   }
296   free(authority);
297   *preq = req;
298   return result;
299 }
300 
301 
302 struct cf_proxy_ctx {
303   /* the protocol specific sub-filter we install during connect */
304   struct Curl_cfilter *cf_protocol;
305 };
306 
http_proxy_cf_connect(struct Curl_cfilter * cf,struct Curl_easy * data,bool blocking,bool * done)307 static CURLcode http_proxy_cf_connect(struct Curl_cfilter *cf,
308                                       struct Curl_easy *data,
309                                       bool blocking, bool *done)
310 {
311   struct cf_proxy_ctx *ctx = cf->ctx;
312   CURLcode result;
313 
314   if(cf->connected) {
315     *done = TRUE;
316     return CURLE_OK;
317   }
318 
319   CURL_TRC_CF(data, cf, "connect");
320 connect_sub:
321   result = cf->next->cft->do_connect(cf->next, data, blocking, done);
322   if(result || !*done)
323     return result;
324 
325   *done = FALSE;
326   if(!ctx->cf_protocol) {
327     struct Curl_cfilter *cf_protocol = NULL;
328     int alpn = Curl_conn_cf_is_ssl(cf->next) ?
329       cf->conn->proxy_alpn : CURL_HTTP_VERSION_1_1;
330 
331     /* First time call after the subchain connected */
332     switch(alpn) {
333     case CURL_HTTP_VERSION_NONE:
334     case CURL_HTTP_VERSION_1_0:
335     case CURL_HTTP_VERSION_1_1:
336       CURL_TRC_CF(data, cf, "installing subfilter for HTTP/1.1");
337       infof(data, "CONNECT tunnel: HTTP/1.%d negotiated",
338             (alpn == CURL_HTTP_VERSION_1_0) ? 0 : 1);
339       result = Curl_cf_h1_proxy_insert_after(cf, data);
340       if(result)
341         goto out;
342       cf_protocol = cf->next;
343       break;
344 #ifdef USE_NGHTTP2
345     case CURL_HTTP_VERSION_2:
346       CURL_TRC_CF(data, cf, "installing subfilter for HTTP/2");
347       infof(data, "CONNECT tunnel: HTTP/2 negotiated");
348       result = Curl_cf_h2_proxy_insert_after(cf, data);
349       if(result)
350         goto out;
351       cf_protocol = cf->next;
352       break;
353 #endif
354     default:
355       infof(data, "CONNECT tunnel: unsupported ALPN(%d) negotiated", alpn);
356       result = CURLE_COULDNT_CONNECT;
357       goto out;
358     }
359 
360     ctx->cf_protocol = cf_protocol;
361     /* after we installed the filter "below" us, we call connect
362      * on out sub-chain again.
363      */
364     goto connect_sub;
365   }
366   else {
367     /* subchain connected and we had already installed the protocol filter.
368      * This means the protocol tunnel is established, we are done.
369      */
370     DEBUGASSERT(ctx->cf_protocol);
371     result = CURLE_OK;
372   }
373 
374 out:
375   if(!result) {
376     cf->connected = TRUE;
377     *done = TRUE;
378   }
379   return result;
380 }
381 
Curl_cf_http_proxy_get_host(struct Curl_cfilter * cf,struct Curl_easy * data,const char ** phost,const char ** pdisplay_host,int * pport)382 void Curl_cf_http_proxy_get_host(struct Curl_cfilter *cf,
383                                  struct Curl_easy *data,
384                                  const char **phost,
385                                  const char **pdisplay_host,
386                                  int *pport)
387 {
388   (void)data;
389   if(!cf->connected) {
390     *phost = cf->conn->http_proxy.host.name;
391     *pdisplay_host = cf->conn->http_proxy.host.dispname;
392     *pport = (int)cf->conn->http_proxy.port;
393   }
394   else {
395     cf->next->cft->get_host(cf->next, data, phost, pdisplay_host, pport);
396   }
397 }
398 
http_proxy_cf_destroy(struct Curl_cfilter * cf,struct Curl_easy * data)399 static void http_proxy_cf_destroy(struct Curl_cfilter *cf,
400                                   struct Curl_easy *data)
401 {
402   struct cf_proxy_ctx *ctx = cf->ctx;
403 
404   (void)data;
405   CURL_TRC_CF(data, cf, "destroy");
406   free(ctx);
407 }
408 
http_proxy_cf_close(struct Curl_cfilter * cf,struct Curl_easy * data)409 static void http_proxy_cf_close(struct Curl_cfilter *cf,
410                                 struct Curl_easy *data)
411 {
412   struct cf_proxy_ctx *ctx = cf->ctx;
413 
414   CURL_TRC_CF(data, cf, "close");
415   cf->connected = FALSE;
416   if(ctx->cf_protocol) {
417     struct Curl_cfilter *f;
418     /* if someone already removed it, we assume he also
419      * took care of destroying it. */
420     for(f = cf->next; f; f = f->next) {
421       if(f == ctx->cf_protocol) {
422         /* still in our sub-chain */
423         Curl_conn_cf_discard_sub(cf, ctx->cf_protocol, data, FALSE);
424         break;
425       }
426     }
427     ctx->cf_protocol = NULL;
428   }
429   if(cf->next)
430     cf->next->cft->do_close(cf->next, data);
431 }
432 
433 
434 struct Curl_cftype Curl_cft_http_proxy = {
435   "HTTP-PROXY",
436   CF_TYPE_IP_CONNECT|CF_TYPE_PROXY,
437   0,
438   http_proxy_cf_destroy,
439   http_proxy_cf_connect,
440   http_proxy_cf_close,
441   Curl_cf_def_shutdown,
442   Curl_cf_http_proxy_get_host,
443   Curl_cf_def_adjust_pollset,
444   Curl_cf_def_data_pending,
445   Curl_cf_def_send,
446   Curl_cf_def_recv,
447   Curl_cf_def_cntrl,
448   Curl_cf_def_conn_is_alive,
449   Curl_cf_def_conn_keep_alive,
450   Curl_cf_def_query,
451 };
452 
Curl_cf_http_proxy_insert_after(struct Curl_cfilter * cf_at,struct Curl_easy * data)453 CURLcode Curl_cf_http_proxy_insert_after(struct Curl_cfilter *cf_at,
454                                          struct Curl_easy *data)
455 {
456   struct Curl_cfilter *cf;
457   struct cf_proxy_ctx *ctx = NULL;
458   CURLcode result;
459 
460   (void)data;
461   ctx = calloc(1, sizeof(*ctx));
462   if(!ctx) {
463     result = CURLE_OUT_OF_MEMORY;
464     goto out;
465   }
466   result = Curl_cf_create(&cf, &Curl_cft_http_proxy, ctx);
467   if(result)
468     goto out;
469   ctx = NULL;
470   Curl_conn_cf_insert_after(cf_at, cf);
471 
472 out:
473   free(ctx);
474   return result;
475 }
476 
477 #endif /* ! CURL_DISABLE_HTTP && !CURL_DISABLE_PROXY */
478