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