1/***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 1998 - 2019, 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.haxx.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 ***************************************************************************/
22
23#include "curl_setup.h"
24
25#include "http_proxy.h"
26
27#if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP)
28
29#include <curl/curl.h>
30#include "sendf.h"
31#include "http.h"
32#include "url.h"
33#include "select.h"
34#include "progress.h"
35#include "non-ascii.h"
36#include "connect.h"
37#include "curlx.h"
38#include "vtls/vtls.h"
39
40/* The last 3 #include files should be in this order */
41#include "curl_printf.h"
42#include "curl_memory.h"
43#include "memdebug.h"
44
45/*
46 * Perform SSL initialization for HTTPS proxy. Sets
47 * proxy_ssl_connected connection bit when complete. Can be
48 * called multiple times.
49 */
50static CURLcode https_proxy_connect(struct connectdata *conn, int sockindex)
51{
52#ifdef USE_SSL
53 CURLcode result = CURLE_OK;
54 DEBUGASSERT(conn->http_proxy.proxytype == CURLPROXY_HTTPS);
55 if(!conn->bits.proxy_ssl_connected[sockindex]) {
56 /* perform SSL initialization for this socket */
57 result =
58 Curl_ssl_connect_nonblocking(conn, sockindex,
59 &conn->bits.proxy_ssl_connected[sockindex]);
60 if(result)
61 conn->bits.close = TRUE; /* a failed connection is marked for closure to
62 prevent (bad) re-use or similar */
63 }
64 return result;
65#else
66 (void) conn;
67 (void) sockindex;
68 return CURLE_NOT_BUILT_IN;
69#endif
70}
71
72CURLcode Curl_proxy_connect(struct connectdata *conn, int sockindex)
73{
74 if(conn->http_proxy.proxytype == CURLPROXY_HTTPS) {
75 const CURLcode result = https_proxy_connect(conn, sockindex);
76 if(result)
77 return result;
78 if(!conn->bits.proxy_ssl_connected[sockindex])
79 return result; /* wait for HTTPS proxy SSL initialization to complete */
80 }
81
82 if(conn->bits.tunnel_proxy && conn->bits.httpproxy) {
83#ifndef CURL_DISABLE_PROXY
84 /* for [protocol] tunneled through HTTP proxy */
85 struct HTTP http_proxy;
86 void *prot_save;
87 const char *hostname;
88 int remote_port;
89 CURLcode result;
90
91 /* BLOCKING */
92 /* We want "seamless" operations through HTTP proxy tunnel */
93
94 /* Curl_proxyCONNECT is based on a pointer to a struct HTTP at the
95 * member conn->proto.http; we want [protocol] through HTTP and we have
96 * to change the member temporarily for connecting to the HTTP
97 * proxy. After Curl_proxyCONNECT we have to set back the member to the
98 * original pointer
99 *
100 * This function might be called several times in the multi interface case
101 * if the proxy's CONNECT response is not instant.
102 */
103 prot_save = conn->data->req.protop;
104 memset(&http_proxy, 0, sizeof(http_proxy));
105 conn->data->req.protop = &http_proxy;
106 connkeep(conn, "HTTP proxy CONNECT");
107
108 /* for the secondary socket (FTP), use the "connect to host"
109 * but ignore the "connect to port" (use the secondary port)
110 */
111
112 if(conn->bits.conn_to_host)
113 hostname = conn->conn_to_host.name;
114 else if(sockindex == SECONDARYSOCKET)
115 hostname = conn->secondaryhostname;
116 else
117 hostname = conn->host.name;
118
119 if(sockindex == SECONDARYSOCKET)
120 remote_port = conn->secondary_port;
121 else if(conn->bits.conn_to_port)
122 remote_port = conn->conn_to_port;
123 else
124 remote_port = conn->remote_port;
125 result = Curl_proxyCONNECT(conn, sockindex, hostname, remote_port);
126 conn->data->req.protop = prot_save;
127 if(CURLE_OK != result)
128 return result;
129 Curl_safefree(conn->allocptr.proxyuserpwd);
130#else
131 return CURLE_NOT_BUILT_IN;
132#endif
133 }
134 /* no HTTP tunnel proxy, just return */
135 return CURLE_OK;
136}
137
138bool Curl_connect_complete(struct connectdata *conn)
139{
140 return !conn->connect_state ||
141 (conn->connect_state->tunnel_state == TUNNEL_COMPLETE);
142}
143
144bool Curl_connect_ongoing(struct connectdata *conn)
145{
146 return conn->connect_state &&
147 (conn->connect_state->tunnel_state != TUNNEL_COMPLETE);
148}
149
150static CURLcode connect_init(struct connectdata *conn, bool reinit)
151{
152 struct http_connect_state *s;
153 if(!reinit) {
154 DEBUGASSERT(!conn->connect_state);
155 s = calloc(1, sizeof(struct http_connect_state));
156 if(!s)
157 return CURLE_OUT_OF_MEMORY;
158 infof(conn->data, "allocate connect buffer!\n");
159 conn->connect_state = s;
160 }
161 else {
162 DEBUGASSERT(conn->connect_state);
163 s = conn->connect_state;
164 }
165 s->tunnel_state = TUNNEL_INIT;
166 s->keepon = TRUE;
167 s->line_start = s->connect_buffer;
168 s->ptr = s->line_start;
169 s->cl = 0;
170 s->close_connection = FALSE;
171 return CURLE_OK;
172}
173
174static void connect_done(struct connectdata *conn)
175{
176 struct http_connect_state *s = conn->connect_state;
177 s->tunnel_state = TUNNEL_COMPLETE;
178 infof(conn->data, "CONNECT phase completed!\n");
179}
180
181static CURLcode CONNECT(struct connectdata *conn,
182 int sockindex,
183 const char *hostname,
184 int remote_port)
185{
186 int subversion = 0;
187 struct Curl_easy *data = conn->data;
188 struct SingleRequest *k = &data->req;
189 CURLcode result;
190 curl_socket_t tunnelsocket = conn->sock[sockindex];
191 struct http_connect_state *s = conn->connect_state;
192
193#define SELECT_OK 0
194#define SELECT_ERROR 1
195
196 if(Curl_connect_complete(conn))
197 return CURLE_OK; /* CONNECT is already completed */
198
199 conn->bits.proxy_connect_closed = FALSE;
200
201 do {
202 timediff_t check;
203 if(TUNNEL_INIT == s->tunnel_state) {
204 /* BEGIN CONNECT PHASE */
205 char *host_port;
206 Curl_send_buffer *req_buffer;
207
208 infof(data, "Establish HTTP proxy tunnel to %s:%d\n",
209 hostname, remote_port);
210
211 /* This only happens if we've looped here due to authentication
212 reasons, and we don't really use the newly cloned URL here
213 then. Just free() it. */
214 free(data->req.newurl);
215 data->req.newurl = NULL;
216
217 /* initialize a dynamic send-buffer */
218 req_buffer = Curl_add_buffer_init();
219
220 if(!req_buffer)
221 return CURLE_OUT_OF_MEMORY;
222
223 host_port = aprintf("%s:%d", hostname, remote_port);
224 if(!host_port) {
225 Curl_add_buffer_free(&req_buffer);
226 return CURLE_OUT_OF_MEMORY;
227 }
228
229 /* Setup the proxy-authorization header, if any */
230 result = Curl_http_output_auth(conn, "CONNECT", host_port, TRUE);
231
232 free(host_port);
233
234 if(!result) {
235 char *host = NULL;
236 const char *proxyconn = "";
237 const char *useragent = "";
238 const char *http = (conn->http_proxy.proxytype == CURLPROXY_HTTP_1_0) ?
239 "1.0" : "1.1";
240 bool ipv6_ip = conn->bits.ipv6_ip;
241 char *hostheader;
242
243 /* the hostname may be different */
244 if(hostname != conn->host.name)
245 ipv6_ip = (strchr(hostname, ':') != NULL);
246 hostheader = /* host:port with IPv6 support */
247 aprintf("%s%s%s:%d", ipv6_ip?"[":"", hostname, ipv6_ip?"]":"",
248 remote_port);
249 if(!hostheader) {
250 Curl_add_buffer_free(&req_buffer);
251 return CURLE_OUT_OF_MEMORY;
252 }
253
254 if(!Curl_checkProxyheaders(conn, "Host")) {
255 host = aprintf("Host: %s\r\n", hostheader);
256 if(!host) {
257 free(hostheader);
258 Curl_add_buffer_free(&req_buffer);
259 return CURLE_OUT_OF_MEMORY;
260 }
261 }
262 if(!Curl_checkProxyheaders(conn, "Proxy-Connection"))
263 proxyconn = "Proxy-Connection: Keep-Alive\r\n";
264
265 if(!Curl_checkProxyheaders(conn, "User-Agent") &&
266 data->set.str[STRING_USERAGENT])
267 useragent = conn->allocptr.uagent;
268
269 result =
270 Curl_add_bufferf(&req_buffer,
271 "CONNECT %s HTTP/%s\r\n"
272 "%s" /* Host: */
273 "%s" /* Proxy-Authorization */
274 "%s" /* User-Agent */
275 "%s", /* Proxy-Connection */
276 hostheader,
277 http,
278 host?host:"",
279 conn->allocptr.proxyuserpwd?
280 conn->allocptr.proxyuserpwd:"",
281 useragent,
282 proxyconn);
283
284 if(host)
285 free(host);
286 free(hostheader);
287
288 if(!result)
289 result = Curl_add_custom_headers(conn, TRUE, req_buffer);
290
291 if(!result)
292 /* CRLF terminate the request */
293 result = Curl_add_bufferf(&req_buffer, "\r\n");
294
295 if(!result) {
296 /* Send the connect request to the proxy */
297 /* BLOCKING */
298 result =
299 Curl_add_buffer_send(&req_buffer, conn,
300 &data->info.request_size, 0, sockindex);
301 }
302 req_buffer = NULL;
303 if(result)
304 failf(data, "Failed sending CONNECT to proxy");
305 }
306
307 Curl_add_buffer_free(&req_buffer);
308 if(result)
309 return result;
310
311 s->tunnel_state = TUNNEL_CONNECT;
312 s->perline = 0;
313 } /* END CONNECT PHASE */
314
315 check = Curl_timeleft(data, NULL, TRUE);
316 if(check <= 0) {
317 failf(data, "Proxy CONNECT aborted due to timeout");
318 return CURLE_OPERATION_TIMEDOUT;
319 }
320
321 if(!Curl_conn_data_pending(conn, sockindex))
322 /* return so we'll be called again polling-style */
323 return CURLE_OK;
324
325 /* at this point, the tunnel_connecting phase is over. */
326
327 { /* READING RESPONSE PHASE */
328 int error = SELECT_OK;
329
330 while(s->keepon) {
331 ssize_t gotbytes;
332
333 /* make sure we have space to read more data */
334 if(s->ptr >= &s->connect_buffer[CONNECT_BUFFER_SIZE]) {
335 failf(data, "CONNECT response too large!");
336 return CURLE_RECV_ERROR;
337 }
338
339 /* Read one byte at a time to avoid a race condition. Wait at most one
340 second before looping to ensure continuous pgrsUpdates. */
341 result = Curl_read(conn, tunnelsocket, s->ptr, 1, &gotbytes);
342 if(result == CURLE_AGAIN)
343 /* socket buffer drained, return */
344 return CURLE_OK;
345
346 if(Curl_pgrsUpdate(conn))
347 return CURLE_ABORTED_BY_CALLBACK;
348
349 if(result) {
350 s->keepon = FALSE;
351 break;
352 }
353 else if(gotbytes <= 0) {
354 if(data->set.proxyauth && data->state.authproxy.avail) {
355 /* proxy auth was requested and there was proxy auth available,
356 then deem this as "mere" proxy disconnect */
357 conn->bits.proxy_connect_closed = TRUE;
358 infof(data, "Proxy CONNECT connection closed\n");
359 }
360 else {
361 error = SELECT_ERROR;
362 failf(data, "Proxy CONNECT aborted");
363 }
364 s->keepon = FALSE;
365 break;
366 }
367
368
369 if(s->keepon > TRUE) {
370 /* This means we are currently ignoring a response-body */
371
372 s->ptr = s->connect_buffer;
373 if(s->cl) {
374 /* A Content-Length based body: simply count down the counter
375 and make sure to break out of the loop when we're done! */
376 s->cl--;
377 if(s->cl <= 0) {
378 s->keepon = FALSE;
379 s->tunnel_state = TUNNEL_COMPLETE;
380 break;
381 }
382 }
383 else {
384 /* chunked-encoded body, so we need to do the chunked dance
385 properly to know when the end of the body is reached */
386 CHUNKcode r;
387 CURLcode extra;
388 ssize_t tookcareof = 0;
389
390 /* now parse the chunked piece of data so that we can
391 properly tell when the stream ends */
392 r = Curl_httpchunk_read(conn, s->ptr, 1, &tookcareof, &extra);
393 if(r == CHUNKE_STOP) {
394 /* we're done reading chunks! */
395 infof(data, "chunk reading DONE\n");
396 s->keepon = FALSE;
397 /* we did the full CONNECT treatment, go COMPLETE */
398 s->tunnel_state = TUNNEL_COMPLETE;
399 }
400 }
401 continue;
402 }
403
404 s->perline++; /* amount of bytes in this line so far */
405
406 /* if this is not the end of a header line then continue */
407 if(*s->ptr != 0x0a) {
408 s->ptr++;
409 continue;
410 }
411
412 /* convert from the network encoding */
413 result = Curl_convert_from_network(data, s->line_start,
414 (size_t)s->perline);
415 /* Curl_convert_from_network calls failf if unsuccessful */
416 if(result)
417 return result;
418
419 /* output debug if that is requested */
420 if(data->set.verbose)
421 Curl_debug(data, CURLINFO_HEADER_IN,
422 s->line_start, (size_t)s->perline);
423
424 if(!data->set.suppress_connect_headers) {
425 /* send the header to the callback */
426 int writetype = CLIENTWRITE_HEADER;
427 if(data->set.include_header)
428 writetype |= CLIENTWRITE_BODY;
429
430 result = Curl_client_write(conn, writetype,
431 s->line_start, s->perline);
432 if(result)
433 return result;
434 }
435
436 data->info.header_size += (long)s->perline;
437 data->req.headerbytecount += (long)s->perline;
438
439 /* Newlines are CRLF, so the CR is ignored as the line isn't
440 really terminated until the LF comes. Treat a following CR
441 as end-of-headers as well.*/
442
443 if(('\r' == s->line_start[0]) ||
444 ('\n' == s->line_start[0])) {
445 /* end of response-headers from the proxy */
446 s->ptr = s->connect_buffer;
447 if((407 == k->httpcode) && !data->state.authproblem) {
448 /* If we get a 407 response code with content length
449 when we have no auth problem, we must ignore the
450 whole response-body */
451 s->keepon = 2;
452
453 if(s->cl) {
454 infof(data, "Ignore %" CURL_FORMAT_CURL_OFF_T
455 " bytes of response-body\n", s->cl);
456 }
457 else if(s->chunked_encoding) {
458 CHUNKcode r;
459 CURLcode extra;
460
461 infof(data, "Ignore chunked response-body\n");
462
463 /* We set ignorebody true here since the chunked
464 decoder function will acknowledge that. Pay
465 attention so that this is cleared again when this
466 function returns! */
467 k->ignorebody = TRUE;
468
469 if(s->line_start[1] == '\n') {
470 /* this can only be a LF if the letter at index 0
471 was a CR */
472 s->line_start++;
473 }
474
475 /* now parse the chunked piece of data so that we can
476 properly tell when the stream ends */
477 r = Curl_httpchunk_read(conn, s->line_start + 1, 1, &gotbytes,
478 &extra);
479 if(r == CHUNKE_STOP) {
480 /* we're done reading chunks! */
481 infof(data, "chunk reading DONE\n");
482 s->keepon = FALSE;
483 /* we did the full CONNECT treatment, go to COMPLETE */
484 s->tunnel_state = TUNNEL_COMPLETE;
485 }
486 }
487 else {
488 /* without content-length or chunked encoding, we
489 can't keep the connection alive since the close is
490 the end signal so we bail out at once instead */
491 s->keepon = FALSE;
492 }
493 }
494 else
495 s->keepon = FALSE;
496 if(!s->cl)
497 /* we did the full CONNECT treatment, go to COMPLETE */
498 s->tunnel_state = TUNNEL_COMPLETE;
499 continue;
500 }
501
502 s->line_start[s->perline] = 0; /* zero terminate the buffer */
503 if((checkprefix("WWW-Authenticate:", s->line_start) &&
504 (401 == k->httpcode)) ||
505 (checkprefix("Proxy-authenticate:", s->line_start) &&
506 (407 == k->httpcode))) {
507
508 bool proxy = (k->httpcode == 407) ? TRUE : FALSE;
509 char *auth = Curl_copy_header_value(s->line_start);
510 if(!auth)
511 return CURLE_OUT_OF_MEMORY;
512
513 result = Curl_http_input_auth(conn, proxy, auth);
514
515 free(auth);
516
517 if(result)
518 return result;
519 }
520 else if(checkprefix("Content-Length:", s->line_start)) {
521 if(k->httpcode/100 == 2) {
522 /* A client MUST ignore any Content-Length or Transfer-Encoding
523 header fields received in a successful response to CONNECT.
524 "Successful" described as: 2xx (Successful). RFC 7231 4.3.6 */
525 infof(data, "Ignoring Content-Length in CONNECT %03d response\n",
526 k->httpcode);
527 }
528 else {
529 (void)curlx_strtoofft(s->line_start +
530 strlen("Content-Length:"), NULL, 10, &s->cl);
531 }
532 }
533 else if(Curl_compareheader(s->line_start, "Connection:", "close"))
534 s->close_connection = TRUE;
535 else if(checkprefix("Transfer-Encoding:", s->line_start)) {
536 if(k->httpcode/100 == 2) {
537 /* A client MUST ignore any Content-Length or Transfer-Encoding
538 header fields received in a successful response to CONNECT.
539 "Successful" described as: 2xx (Successful). RFC 7231 4.3.6 */
540 infof(data, "Ignoring Transfer-Encoding in "
541 "CONNECT %03d response\n", k->httpcode);
542 }
543 else if(Curl_compareheader(s->line_start,
544 "Transfer-Encoding:", "chunked")) {
545 infof(data, "CONNECT responded chunked\n");
546 s->chunked_encoding = TRUE;
547 /* init our chunky engine */
548 Curl_httpchunk_init(conn);
549 }
550 }
551 else if(Curl_compareheader(s->line_start,
552 "Proxy-Connection:", "close"))
553 s->close_connection = TRUE;
554 else if(2 == sscanf(s->line_start, "HTTP/1.%d %d",
555 &subversion,
556 &k->httpcode)) {
557 /* store the HTTP code from the proxy */
558 data->info.httpproxycode = k->httpcode;
559 }
560
561 s->perline = 0; /* line starts over here */
562 s->ptr = s->connect_buffer;
563 s->line_start = s->ptr;
564 } /* while there's buffer left and loop is requested */
565
566 if(Curl_pgrsUpdate(conn))
567 return CURLE_ABORTED_BY_CALLBACK;
568
569 if(error)
570 return CURLE_RECV_ERROR;
571
572 if(data->info.httpproxycode/100 != 2) {
573 /* Deal with the possibly already received authenticate
574 headers. 'newurl' is set to a new URL if we must loop. */
575 result = Curl_http_auth_act(conn);
576 if(result)
577 return result;
578
579 if(conn->bits.close)
580 /* the connection has been marked for closure, most likely in the
581 Curl_http_auth_act() function and thus we can kill it at once
582 below */
583 s->close_connection = TRUE;
584 }
585
586 if(s->close_connection && data->req.newurl) {
587 /* Connection closed by server. Don't use it anymore */
588 Curl_closesocket(conn, conn->sock[sockindex]);
589 conn->sock[sockindex] = CURL_SOCKET_BAD;
590 break;
591 }
592 } /* END READING RESPONSE PHASE */
593
594 /* If we are supposed to continue and request a new URL, which basically
595 * means the HTTP authentication is still going on so if the tunnel
596 * is complete we start over in INIT state */
597 if(data->req.newurl && (TUNNEL_COMPLETE == s->tunnel_state)) {
598 connect_init(conn, TRUE); /* reinit */
599 }
600
601 } while(data->req.newurl);
602
603 if(data->info.httpproxycode/100 != 2) {
604 if(s->close_connection && data->req.newurl) {
605 conn->bits.proxy_connect_closed = TRUE;
606 infof(data, "Connect me again please\n");
607 connect_done(conn);
608 }
609 else {
610 free(data->req.newurl);
611 data->req.newurl = NULL;
612 /* failure, close this connection to avoid re-use */
613 streamclose(conn, "proxy CONNECT failure");
614 Curl_closesocket(conn, conn->sock[sockindex]);
615 conn->sock[sockindex] = CURL_SOCKET_BAD;
616 }
617
618 /* to back to init state */
619 s->tunnel_state = TUNNEL_INIT;
620
621 if(conn->bits.proxy_connect_closed)
622 /* this is not an error, just part of the connection negotiation */
623 return CURLE_OK;
624 failf(data, "Received HTTP code %d from proxy after CONNECT",
625 data->req.httpcode);
626 return CURLE_RECV_ERROR;
627 }
628
629 s->tunnel_state = TUNNEL_COMPLETE;
630
631 /* If a proxy-authorization header was used for the proxy, then we should
632 make sure that it isn't accidentally used for the document request
633 after we've connected. So let's free and clear it here. */
634 Curl_safefree(conn->allocptr.proxyuserpwd);
635 conn->allocptr.proxyuserpwd = NULL;
636
637 data->state.authproxy.done = TRUE;
638 data->state.authproxy.multipass = FALSE;
639
640 infof(data, "Proxy replied %d to CONNECT request\n",
641 data->info.httpproxycode);
642 data->req.ignorebody = FALSE; /* put it (back) to non-ignore state */
643 conn->bits.rewindaftersend = FALSE; /* make sure this isn't set for the
644 document request */
645 return CURLE_OK;
646}
647
648void Curl_connect_free(struct Curl_easy *data)
649{
650 struct connectdata *conn = data->conn;
651 struct http_connect_state *s = conn->connect_state;
652 if(s) {
653 free(s);
654 conn->connect_state = NULL;
655 }
656}
657
658/*
659 * Curl_proxyCONNECT() requires that we're connected to a HTTP proxy. This
660 * function will issue the necessary commands to get a seamless tunnel through
661 * this proxy. After that, the socket can be used just as a normal socket.
662 */
663
664CURLcode Curl_proxyCONNECT(struct connectdata *conn,
665 int sockindex,
666 const char *hostname,
667 int remote_port)
668{
669 CURLcode result;
670 if(!conn->connect_state) {
671 result = connect_init(conn, FALSE);
672 if(result)
673 return result;
674 }
675 result = CONNECT(conn, sockindex, hostname, remote_port);
676
677 if(result || Curl_connect_complete(conn))
678 connect_done(conn);
679
680 return result;
681}
682
683#else
684void Curl_connect_free(struct Curl_easy *data)
685{
686 (void)data;
687}
688
689#endif /* CURL_DISABLE_PROXY */
690