1 /***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 1998 - 2017, 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 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
Curl_proxy_connect(struct connectdata * conn,int sockindex)72 CURLcode 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
Curl_connect_complete(struct connectdata * conn)138 bool Curl_connect_complete(struct connectdata *conn)
139 {
140 return !conn->connect_state ||
141 (conn->connect_state->tunnel_state == TUNNEL_COMPLETE);
142 }
143
Curl_connect_ongoing(struct connectdata * conn)144 bool Curl_connect_ongoing(struct connectdata *conn)
145 {
146 return conn->connect_state &&
147 (conn->connect_state->tunnel_state != TUNNEL_COMPLETE);
148 }
149
connect_init(struct connectdata * conn,bool reinit)150 static 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
connect_done(struct connectdata * conn)174 static 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
CONNECT(struct connectdata * conn,int sockindex,const char * hostname,int remote_port)181 static 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 timediff_t check;
192 struct http_connect_state *s = conn->connect_state;
193
194 #define SELECT_OK 0
195 #define SELECT_ERROR 1
196 #define SELECT_TIMEOUT 2
197
198 if(Curl_connect_complete(conn))
199 return CURLE_OK; /* CONNECT is already completed */
200
201 conn->bits.proxy_connect_closed = FALSE;
202
203 do {
204 if(TUNNEL_INIT == s->tunnel_state) {
205 /* BEGIN CONNECT PHASE */
206 char *host_port;
207 Curl_send_buffer *req_buffer;
208
209 infof(data, "Establish HTTP proxy tunnel to %s:%hu\n",
210 hostname, remote_port);
211
212 /* This only happens if we've looped here due to authentication
213 reasons, and we don't really use the newly cloned URL here
214 then. Just free() it. */
215 free(data->req.newurl);
216 data->req.newurl = NULL;
217
218 /* initialize a dynamic send-buffer */
219 req_buffer = Curl_add_buffer_init();
220
221 if(!req_buffer)
222 return CURLE_OUT_OF_MEMORY;
223
224 host_port = aprintf("%s:%hu", hostname, remote_port);
225 if(!host_port) {
226 Curl_add_buffer_free(req_buffer);
227 return CURLE_OUT_OF_MEMORY;
228 }
229
230 /* Setup the proxy-authorization header, if any */
231 result = Curl_http_output_auth(conn, "CONNECT", host_port, TRUE);
232
233 free(host_port);
234
235 if(!result) {
236 char *host = NULL;
237 const char *proxyconn = "";
238 const char *useragent = "";
239 const char *http = (conn->http_proxy.proxytype == CURLPROXY_HTTP_1_0) ?
240 "1.0" : "1.1";
241 bool ipv6_ip = conn->bits.ipv6_ip;
242 char *hostheader;
243
244 /* the hostname may be different */
245 if(hostname != conn->host.name)
246 ipv6_ip = (strchr(hostname, ':') != NULL);
247 hostheader = /* host:port with IPv6 support */
248 aprintf("%s%s%s:%hu", ipv6_ip?"[":"", hostname, ipv6_ip?"]":"",
249 remote_port);
250 if(!hostheader) {
251 Curl_add_buffer_free(req_buffer);
252 return CURLE_OUT_OF_MEMORY;
253 }
254
255 if(!Curl_checkProxyheaders(conn, "Host:")) {
256 host = aprintf("Host: %s\r\n", hostheader);
257 if(!host) {
258 free(hostheader);
259 Curl_add_buffer_free(req_buffer);
260 return CURLE_OUT_OF_MEMORY;
261 }
262 }
263 if(!Curl_checkProxyheaders(conn, "Proxy-Connection:"))
264 proxyconn = "Proxy-Connection: Keep-Alive\r\n";
265
266 if(!Curl_checkProxyheaders(conn, "User-Agent:") &&
267 data->set.str[STRING_USERAGENT])
268 useragent = conn->allocptr.uagent;
269
270 result =
271 Curl_add_bufferf(req_buffer,
272 "CONNECT %s HTTP/%s\r\n"
273 "%s" /* Host: */
274 "%s" /* Proxy-Authorization */
275 "%s" /* User-Agent */
276 "%s", /* Proxy-Connection */
277 hostheader,
278 http,
279 host?host:"",
280 conn->allocptr.proxyuserpwd?
281 conn->allocptr.proxyuserpwd:"",
282 useragent,
283 proxyconn);
284
285 if(host)
286 free(host);
287 free(hostheader);
288
289 if(!result)
290 result = Curl_add_custom_headers(conn, TRUE, req_buffer);
291
292 if(!result)
293 /* CRLF terminate the request */
294 result = Curl_add_bufferf(req_buffer, "\r\n");
295
296 if(!result) {
297 /* Send the connect request to the proxy */
298 /* BLOCKING */
299 result =
300 Curl_add_buffer_send(req_buffer, conn,
301 &data->info.request_size, 0, sockindex);
302 }
303 req_buffer = NULL;
304 if(result)
305 failf(data, "Failed sending CONNECT to proxy");
306 }
307
308 Curl_add_buffer_free(req_buffer);
309 if(result)
310 return result;
311
312 s->tunnel_state = TUNNEL_CONNECT;
313 s->perline = 0;
314 } /* END CONNECT PHASE */
315
316 check = Curl_timeleft(data, NULL, TRUE);
317 if(check <= 0) {
318 failf(data, "Proxy CONNECT aborted due to timeout");
319 return CURLE_OPERATION_TIMEDOUT;
320 }
321
322 if(!Curl_conn_data_pending(conn, sockindex))
323 /* return so we'll be called again polling-style */
324 return CURLE_OK;
325
326 /* at this point, the tunnel_connecting phase is over. */
327
328 { /* READING RESPONSE PHASE */
329 int error = SELECT_OK;
330
331 while(s->keepon && !error) {
332 ssize_t gotbytes;
333
334 /* make sure we have space to read more data */
335 if(s->ptr >= &s->connect_buffer[CONNECT_BUFFER_SIZE]) {
336 failf(data, "CONNECT response too large!");
337 return CURLE_RECV_ERROR;
338 }
339
340 /* Read one byte at a time to avoid a race condition. Wait at most one
341 second before looping to ensure continuous pgrsUpdates. */
342 result = Curl_read(conn, tunnelsocket, s->ptr, 1, &gotbytes);
343 if(result == CURLE_AGAIN)
344 /* socket buffer drained, return */
345 return CURLE_OK;
346
347 if(Curl_pgrsUpdate(conn))
348 return CURLE_ABORTED_BY_CALLBACK;
349
350 if(result) {
351 s->keepon = FALSE;
352 break;
353 }
354 else if(gotbytes <= 0) {
355 if(data->set.proxyauth && data->state.authproxy.avail) {
356 /* proxy auth was requested and there was proxy auth available,
357 then deem this as "mere" proxy disconnect */
358 conn->bits.proxy_connect_closed = TRUE;
359 infof(data, "Proxy CONNECT connection closed\n");
360 }
361 else {
362 error = SELECT_ERROR;
363 failf(data, "Proxy CONNECT aborted");
364 }
365 s->keepon = FALSE;
366 break;
367 }
368
369
370 if(s->keepon > TRUE) {
371 /* This means we are currently ignoring a response-body */
372
373 s->ptr = s->connect_buffer;
374 if(s->cl) {
375 /* A Content-Length based body: simply count down the counter
376 and make sure to break out of the loop when we're done! */
377 s->cl--;
378 if(s->cl <= 0) {
379 s->keepon = FALSE;
380 s->tunnel_state = TUNNEL_COMPLETE;
381 break;
382 }
383 }
384 else {
385 /* chunked-encoded body, so we need to do the chunked dance
386 properly to know when the end of the body is reached */
387 CHUNKcode r;
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);
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, conn);
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
460 infof(data, "Ignore chunked response-body\n");
461
462 /* We set ignorebody true here since the chunked
463 decoder function will acknowledge that. Pay
464 attention so that this is cleared again when this
465 function returns! */
466 k->ignorebody = TRUE;
467
468 if(s->line_start[1] == '\n') {
469 /* this can only be a LF if the letter at index 0
470 was a CR */
471 s->line_start++;
472 }
473
474 /* now parse the chunked piece of data so that we can
475 properly tell when the stream ends */
476 r = Curl_httpchunk_read(conn, s->line_start + 1, 1, &gotbytes);
477 if(r == CHUNKE_STOP) {
478 /* we're done reading chunks! */
479 infof(data, "chunk reading DONE\n");
480 s->keepon = FALSE;
481 /* we did the full CONNECT treatment, go to COMPLETE */
482 s->tunnel_state = TUNNEL_COMPLETE;
483 }
484 }
485 else {
486 /* without content-length or chunked encoding, we
487 can't keep the connection alive since the close is
488 the end signal so we bail out at once instead */
489 s->keepon = FALSE;
490 }
491 }
492 else
493 s->keepon = FALSE;
494 if(!s->cl)
495 /* we did the full CONNECT treatment, go to COMPLETE */
496 s->tunnel_state = TUNNEL_COMPLETE;
497 continue;
498 }
499
500 s->line_start[s->perline] = 0; /* zero terminate the buffer */
501 if((checkprefix("WWW-Authenticate:", s->line_start) &&
502 (401 == k->httpcode)) ||
503 (checkprefix("Proxy-authenticate:", s->line_start) &&
504 (407 == k->httpcode))) {
505
506 bool proxy = (k->httpcode == 407) ? TRUE : FALSE;
507 char *auth = Curl_copy_header_value(s->line_start);
508 if(!auth)
509 return CURLE_OUT_OF_MEMORY;
510
511 result = Curl_http_input_auth(conn, proxy, auth);
512
513 free(auth);
514
515 if(result)
516 return result;
517 }
518 else if(checkprefix("Content-Length:", s->line_start)) {
519 if(k->httpcode/100 == 2) {
520 /* A client MUST ignore any Content-Length or Transfer-Encoding
521 header fields received in a successful response to CONNECT.
522 "Successful" described as: 2xx (Successful). RFC 7231 4.3.6 */
523 infof(data, "Ignoring Content-Length in CONNECT %03d response\n",
524 k->httpcode);
525 }
526 else {
527 (void)curlx_strtoofft(s->line_start +
528 strlen("Content-Length:"), NULL, 10, &s->cl);
529 }
530 }
531 else if(Curl_compareheader(s->line_start, "Connection:", "close"))
532 s->close_connection = TRUE;
533 else if(checkprefix("Transfer-Encoding:", s->line_start)) {
534 if(k->httpcode/100 == 2) {
535 /* A client MUST ignore any Content-Length or Transfer-Encoding
536 header fields received in a successful response to CONNECT.
537 "Successful" described as: 2xx (Successful). RFC 7231 4.3.6 */
538 infof(data, "Ignoring Transfer-Encoding in "
539 "CONNECT %03d response\n", k->httpcode);
540 }
541 else if(Curl_compareheader(s->line_start,
542 "Transfer-Encoding:", "chunked")) {
543 infof(data, "CONNECT responded chunked\n");
544 s->chunked_encoding = TRUE;
545 /* init our chunky engine */
546 Curl_httpchunk_init(conn);
547 }
548 }
549 else if(Curl_compareheader(s->line_start,
550 "Proxy-Connection:", "close"))
551 s->close_connection = TRUE;
552 else if(2 == sscanf(s->line_start, "HTTP/1.%d %d",
553 &subversion,
554 &k->httpcode)) {
555 /* store the HTTP code from the proxy */
556 data->info.httpproxycode = k->httpcode;
557 }
558
559 s->perline = 0; /* line starts over here */
560 s->ptr = s->connect_buffer;
561 s->line_start = s->ptr;
562 } /* while there's buffer left and loop is requested */
563
564 if(Curl_pgrsUpdate(conn))
565 return CURLE_ABORTED_BY_CALLBACK;
566
567 if(error)
568 return CURLE_RECV_ERROR;
569
570 if(data->info.httpproxycode/100 != 2) {
571 /* Deal with the possibly already received authenticate
572 headers. 'newurl' is set to a new URL if we must loop. */
573 result = Curl_http_auth_act(conn);
574 if(result)
575 return result;
576
577 if(conn->bits.close)
578 /* the connection has been marked for closure, most likely in the
579 Curl_http_auth_act() function and thus we can kill it at once
580 below */
581 s->close_connection = TRUE;
582 }
583
584 if(s->close_connection && data->req.newurl) {
585 /* Connection closed by server. Don't use it anymore */
586 Curl_closesocket(conn, conn->sock[sockindex]);
587 conn->sock[sockindex] = CURL_SOCKET_BAD;
588 break;
589 }
590 } /* END READING RESPONSE PHASE */
591
592 /* If we are supposed to continue and request a new URL, which basically
593 * means the HTTP authentication is still going on so if the tunnel
594 * is complete we start over in INIT state */
595 if(data->req.newurl && (TUNNEL_COMPLETE == s->tunnel_state)) {
596 connect_init(conn, TRUE); /* reinit */
597 }
598
599 } while(data->req.newurl);
600
601 if(data->info.httpproxycode/100 != 2) {
602 if(s->close_connection && data->req.newurl) {
603 conn->bits.proxy_connect_closed = TRUE;
604 infof(data, "Connect me again please\n");
605 connect_done(conn);
606 }
607 else {
608 free(data->req.newurl);
609 data->req.newurl = NULL;
610 /* failure, close this connection to avoid re-use */
611 streamclose(conn, "proxy CONNECT failure");
612 Curl_closesocket(conn, conn->sock[sockindex]);
613 conn->sock[sockindex] = CURL_SOCKET_BAD;
614 }
615
616 /* to back to init state */
617 s->tunnel_state = TUNNEL_INIT;
618
619 if(conn->bits.proxy_connect_closed)
620 /* this is not an error, just part of the connection negotiation */
621 return CURLE_OK;
622 failf(data, "Received HTTP code %d from proxy after CONNECT",
623 data->req.httpcode);
624 return CURLE_RECV_ERROR;
625 }
626
627 s->tunnel_state = TUNNEL_COMPLETE;
628
629 /* If a proxy-authorization header was used for the proxy, then we should
630 make sure that it isn't accidentally used for the document request
631 after we've connected. So let's free and clear it here. */
632 Curl_safefree(conn->allocptr.proxyuserpwd);
633 conn->allocptr.proxyuserpwd = NULL;
634
635 data->state.authproxy.done = TRUE;
636
637 infof(data, "Proxy replied %d to CONNECT request\n",
638 data->info.httpproxycode);
639 data->req.ignorebody = FALSE; /* put it (back) to non-ignore state */
640 conn->bits.rewindaftersend = FALSE; /* make sure this isn't set for the
641 document request */
642 return CURLE_OK;
643 }
644
Curl_connect_free(struct Curl_easy * data)645 void Curl_connect_free(struct Curl_easy *data)
646 {
647 struct connectdata *conn = data->easy_conn;
648 struct http_connect_state *s = conn->connect_state;
649 if(s) {
650 free(s);
651 conn->connect_state = NULL;
652 }
653 }
654
655 /*
656 * Curl_proxyCONNECT() requires that we're connected to a HTTP proxy. This
657 * function will issue the necessary commands to get a seamless tunnel through
658 * this proxy. After that, the socket can be used just as a normal socket.
659 */
660
Curl_proxyCONNECT(struct connectdata * conn,int sockindex,const char * hostname,int remote_port)661 CURLcode Curl_proxyCONNECT(struct connectdata *conn,
662 int sockindex,
663 const char *hostname,
664 int remote_port)
665 {
666 CURLcode result;
667 if(!conn->connect_state) {
668 result = connect_init(conn, FALSE);
669 if(result)
670 return result;
671 }
672 result = CONNECT(conn, sockindex, hostname, remote_port);
673
674 if(result || Curl_connect_complete(conn))
675 connect_done(conn);
676
677 return result;
678 }
679
680 #else
Curl_connect_free(struct Curl_easy * data)681 void Curl_connect_free(struct Curl_easy *data)
682 {
683 (void)data;
684 }
685
686 #endif /* CURL_DISABLE_PROXY */
687