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