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