1 /***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 1998 - 2016, 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 #if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP)
26
27 #include "urldata.h"
28 #include <curl/curl.h>
29 #include "http_proxy.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 CONNTECT 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 if(sockindex == SECONDARYSOCKET)
108 hostname = conn->secondaryhostname;
109 else if(conn->bits.conn_to_host)
110 hostname = conn->conn_to_host.name;
111 else
112 hostname = conn->host.name;
113
114 if(sockindex == SECONDARYSOCKET)
115 remote_port = conn->secondary_port;
116 else if(conn->bits.conn_to_port)
117 remote_port = conn->conn_to_port;
118 else
119 remote_port = conn->remote_port;
120 result = Curl_proxyCONNECT(conn, sockindex, hostname,
121 remote_port, FALSE);
122 conn->data->req.protop = prot_save;
123 if(CURLE_OK != result)
124 return result;
125 Curl_safefree(conn->allocptr.proxyuserpwd);
126 #else
127 return CURLE_NOT_BUILT_IN;
128 #endif
129 }
130 /* no HTTP tunnel proxy, just return */
131 return CURLE_OK;
132 }
133
134 /*
135 * Curl_proxyCONNECT() requires that we're connected to a HTTP proxy. This
136 * function will issue the necessary commands to get a seamless tunnel through
137 * this proxy. After that, the socket can be used just as a normal socket.
138 *
139 * 'blocking' set to TRUE means that this function will do the entire CONNECT
140 * + response in a blocking fashion. Should be avoided!
141 */
142
Curl_proxyCONNECT(struct connectdata * conn,int sockindex,const char * hostname,int remote_port,bool blocking)143 CURLcode Curl_proxyCONNECT(struct connectdata *conn,
144 int sockindex,
145 const char *hostname,
146 int remote_port,
147 bool blocking)
148 {
149 int subversion=0;
150 struct Curl_easy *data=conn->data;
151 struct SingleRequest *k = &data->req;
152 CURLcode result;
153 curl_socket_t tunnelsocket = conn->sock[sockindex];
154 curl_off_t cl=0;
155 bool closeConnection = FALSE;
156 bool chunked_encoding = FALSE;
157 time_t check;
158
159 #define SELECT_OK 0
160 #define SELECT_ERROR 1
161 #define SELECT_TIMEOUT 2
162 int error = SELECT_OK;
163
164 if(conn->tunnel_state[sockindex] == TUNNEL_COMPLETE)
165 return CURLE_OK; /* CONNECT is already completed */
166
167 conn->bits.proxy_connect_closed = FALSE;
168
169 do {
170 if(TUNNEL_INIT == conn->tunnel_state[sockindex]) {
171 /* BEGIN CONNECT PHASE */
172 char *host_port;
173 Curl_send_buffer *req_buffer;
174
175 infof(data, "Establish HTTP proxy tunnel to %s:%hu\n",
176 hostname, remote_port);
177
178 /* This only happens if we've looped here due to authentication
179 reasons, and we don't really use the newly cloned URL here
180 then. Just free() it. */
181 free(data->req.newurl);
182 data->req.newurl = NULL;
183
184 /* initialize a dynamic send-buffer */
185 req_buffer = Curl_add_buffer_init();
186
187 if(!req_buffer)
188 return CURLE_OUT_OF_MEMORY;
189
190 host_port = aprintf("%s:%hu", hostname, remote_port);
191 if(!host_port) {
192 Curl_add_buffer_free(req_buffer);
193 return CURLE_OUT_OF_MEMORY;
194 }
195
196 /* Setup the proxy-authorization header, if any */
197 result = Curl_http_output_auth(conn, "CONNECT", host_port, TRUE);
198
199 free(host_port);
200
201 if(!result) {
202 char *host=(char *)"";
203 const char *proxyconn="";
204 const char *useragent="";
205 const char *http = (conn->http_proxy.proxytype == CURLPROXY_HTTP_1_0) ?
206 "1.0" : "1.1";
207 bool ipv6_ip = conn->bits.ipv6_ip;
208 char *hostheader;
209
210 /* the hostname may be different */
211 if(hostname != conn->host.name)
212 ipv6_ip = (strchr(hostname, ':') != NULL);
213 hostheader= /* host:port with IPv6 support */
214 aprintf("%s%s%s:%hu", ipv6_ip?"[":"", hostname, ipv6_ip?"]":"",
215 remote_port);
216 if(!hostheader) {
217 Curl_add_buffer_free(req_buffer);
218 return CURLE_OUT_OF_MEMORY;
219 }
220
221 if(!Curl_checkProxyheaders(conn, "Host:")) {
222 host = aprintf("Host: %s\r\n", hostheader);
223 if(!host) {
224 free(hostheader);
225 Curl_add_buffer_free(req_buffer);
226 return CURLE_OUT_OF_MEMORY;
227 }
228 }
229 if(!Curl_checkProxyheaders(conn, "Proxy-Connection:"))
230 proxyconn = "Proxy-Connection: Keep-Alive\r\n";
231
232 if(!Curl_checkProxyheaders(conn, "User-Agent:") &&
233 data->set.str[STRING_USERAGENT])
234 useragent = conn->allocptr.uagent;
235
236 result =
237 Curl_add_bufferf(req_buffer,
238 "CONNECT %s HTTP/%s\r\n"
239 "%s" /* Host: */
240 "%s" /* Proxy-Authorization */
241 "%s" /* User-Agent */
242 "%s", /* Proxy-Connection */
243 hostheader,
244 http,
245 host,
246 conn->allocptr.proxyuserpwd?
247 conn->allocptr.proxyuserpwd:"",
248 useragent,
249 proxyconn);
250
251 if(host && *host)
252 free(host);
253 free(hostheader);
254
255 if(!result)
256 result = Curl_add_custom_headers(conn, TRUE, req_buffer);
257
258 if(!result)
259 /* CRLF terminate the request */
260 result = Curl_add_bufferf(req_buffer, "\r\n");
261
262 if(!result) {
263 /* Send the connect request to the proxy */
264 /* BLOCKING */
265 result =
266 Curl_add_buffer_send(req_buffer, conn,
267 &data->info.request_size, 0, sockindex);
268 }
269 req_buffer = NULL;
270 if(result)
271 failf(data, "Failed sending CONNECT to proxy");
272 }
273
274 Curl_add_buffer_free(req_buffer);
275 if(result)
276 return result;
277
278 conn->tunnel_state[sockindex] = TUNNEL_CONNECT;
279 } /* END CONNECT PHASE */
280
281 check = Curl_timeleft(data, NULL, TRUE);
282 if(check <= 0) {
283 failf(data, "Proxy CONNECT aborted due to timeout");
284 return CURLE_RECV_ERROR;
285 }
286
287 if(!blocking) {
288 if(0 == SOCKET_READABLE(tunnelsocket, 0))
289 /* return so we'll be called again polling-style */
290 return CURLE_OK;
291 else {
292 DEBUGF(infof(data,
293 "Read response immediately from proxy CONNECT\n"));
294 }
295 }
296
297 /* at this point, the tunnel_connecting phase is over. */
298
299 { /* READING RESPONSE PHASE */
300 size_t nread; /* total size read */
301 int perline; /* count bytes per line */
302 int keepon=TRUE;
303 ssize_t gotbytes;
304 char *ptr;
305 char *line_start;
306
307 ptr=data->state.buffer;
308 line_start = ptr;
309
310 nread=0;
311 perline=0;
312
313 while((nread<BUFSIZE) && (keepon && !error)) {
314
315 check = Curl_timeleft(data, NULL, TRUE);
316 if(check <= 0) {
317 failf(data, "Proxy CONNECT aborted due to timeout");
318 error = SELECT_TIMEOUT; /* already too little time */
319 break;
320 }
321
322 /* loop every second at least, less if the timeout is near */
323 switch (SOCKET_READABLE(tunnelsocket, check<1000L?check:1000)) {
324 case -1: /* select() error, stop reading */
325 error = SELECT_ERROR;
326 failf(data, "Proxy CONNECT aborted due to select/poll error");
327 break;
328 case 0: /* timeout */
329 break;
330 default:
331 DEBUGASSERT(ptr+BUFSIZE-nread <= data->state.buffer+BUFSIZE+1);
332 result = Curl_read(conn, tunnelsocket, ptr, BUFSIZE-nread,
333 &gotbytes);
334 if(result==CURLE_AGAIN)
335 continue; /* go loop yourself */
336 else if(result)
337 keepon = FALSE;
338 else if(gotbytes <= 0) {
339 keepon = FALSE;
340 if(data->set.proxyauth && data->state.authproxy.avail) {
341 /* proxy auth was requested and there was proxy auth available,
342 then deem this as "mere" proxy disconnect */
343 conn->bits.proxy_connect_closed = TRUE;
344 infof(data, "Proxy CONNECT connection closed\n");
345 }
346 else {
347 error = SELECT_ERROR;
348 failf(data, "Proxy CONNECT aborted");
349 }
350 }
351 else {
352 /*
353 * We got a whole chunk of data, which can be anything from one
354 * byte to a set of lines and possibly just a piece of the last
355 * line.
356 */
357 int i;
358
359 nread += gotbytes;
360
361 if(keepon > TRUE) {
362 /* This means we are currently ignoring a response-body */
363
364 nread = 0; /* make next read start over in the read buffer */
365 ptr=data->state.buffer;
366 if(cl) {
367 /* A Content-Length based body: simply count down the counter
368 and make sure to break out of the loop when we're done! */
369 cl -= gotbytes;
370 if(cl<=0) {
371 keepon = FALSE;
372 break;
373 }
374 }
375 else {
376 /* chunked-encoded body, so we need to do the chunked dance
377 properly to know when the end of the body is reached */
378 CHUNKcode r;
379 ssize_t tookcareof=0;
380
381 /* now parse the chunked piece of data so that we can
382 properly tell when the stream ends */
383 r = Curl_httpchunk_read(conn, ptr, gotbytes, &tookcareof);
384 if(r == CHUNKE_STOP) {
385 /* we're done reading chunks! */
386 infof(data, "chunk reading DONE\n");
387 keepon = FALSE;
388 /* we did the full CONNECT treatment, go COMPLETE */
389 conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
390 }
391 else
392 infof(data, "Read %zd bytes of chunk, continue\n",
393 tookcareof);
394 }
395 }
396 else
397 for(i = 0; i < gotbytes; ptr++, i++) {
398 perline++; /* amount of bytes in this line so far */
399 if(*ptr == 0x0a) {
400 char letter;
401 int writetype;
402
403 /* convert from the network encoding */
404 result = Curl_convert_from_network(data, line_start,
405 perline);
406 /* Curl_convert_from_network calls failf if unsuccessful */
407 if(result)
408 return result;
409
410 /* output debug if that is requested */
411 if(data->set.verbose)
412 Curl_debug(data, CURLINFO_HEADER_IN,
413 line_start, (size_t)perline, conn);
414
415 /* send the header to the callback */
416 writetype = CLIENTWRITE_HEADER;
417 if(data->set.include_header)
418 writetype |= CLIENTWRITE_BODY;
419
420 result = Curl_client_write(conn, writetype, line_start,
421 perline);
422
423 data->info.header_size += (long)perline;
424 data->req.headerbytecount += (long)perline;
425
426 if(result)
427 return result;
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' == line_start[0]) ||
434 ('\n' == line_start[0])) {
435 /* end of response-headers from the proxy */
436 nread = 0; /* make next read start over in the read
437 buffer */
438 ptr=data->state.buffer;
439 if((407 == k->httpcode) && !data->state.authproblem) {
440 /* If we get a 407 response code with content length
441 when we have no auth problem, we must ignore the
442 whole response-body */
443 keepon = 2;
444
445 if(cl) {
446 infof(data, "Ignore %" CURL_FORMAT_CURL_OFF_T
447 " bytes of response-body\n", cl);
448
449 /* remove the remaining chunk of what we already
450 read */
451 cl -= (gotbytes - i);
452
453 if(cl<=0)
454 /* if the whole thing was already read, we are done!
455 */
456 keepon=FALSE;
457 }
458 else if(chunked_encoding) {
459 CHUNKcode r;
460 /* We set ignorebody true here since the chunked
461 decoder function will acknowledge that. Pay
462 attention so that this is cleared again when this
463 function returns! */
464 k->ignorebody = TRUE;
465 infof(data, "%zd bytes of chunk left\n", gotbytes-i);
466
467 if(line_start[1] == '\n') {
468 /* this can only be a LF if the letter at index 0
469 was a CR */
470 line_start++;
471 i++;
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, line_start+1,
477 gotbytes -i, &gotbytes);
478 if(r == CHUNKE_STOP) {
479 /* we're done reading chunks! */
480 infof(data, "chunk reading DONE\n");
481 keepon = FALSE;
482 /* we did the full CONNECT treatment, go to
483 COMPLETE */
484 conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
485 }
486 else
487 infof(data, "Read %zd bytes of chunk, continue\n",
488 gotbytes);
489 }
490 else {
491 /* without content-length or chunked encoding, we
492 can't keep the connection alive since the close is
493 the end signal so we bail out at once instead */
494 keepon=FALSE;
495 }
496 }
497 else {
498 keepon = FALSE;
499 if(200 == data->info.httpproxycode) {
500 if(gotbytes - (i+1))
501 failf(data, "Proxy CONNECT followed by %zd bytes "
502 "of opaque data. Data ignored (known bug #39)",
503 gotbytes - (i+1));
504 }
505 }
506 /* we did the full CONNECT treatment, go to COMPLETE */
507 conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
508 break; /* breaks out of for-loop, not switch() */
509 }
510
511 /* keep a backup of the position we are about to blank */
512 letter = line_start[perline];
513 line_start[perline]=0; /* zero terminate the buffer */
514 if((checkprefix("WWW-Authenticate:", line_start) &&
515 (401 == k->httpcode)) ||
516 (checkprefix("Proxy-authenticate:", line_start) &&
517 (407 == k->httpcode))) {
518
519 bool proxy = (k->httpcode == 407) ? TRUE : FALSE;
520 char *auth = Curl_copy_header_value(line_start);
521 if(!auth)
522 return CURLE_OUT_OF_MEMORY;
523
524 result = Curl_http_input_auth(conn, proxy, auth);
525
526 free(auth);
527
528 if(result)
529 return result;
530 }
531 else if(checkprefix("Content-Length:", line_start)) {
532 cl = curlx_strtoofft(line_start +
533 strlen("Content-Length:"), NULL, 10);
534 }
535 else if(Curl_compareheader(line_start,
536 "Connection:", "close"))
537 closeConnection = TRUE;
538 else if(Curl_compareheader(line_start,
539 "Transfer-Encoding:",
540 "chunked")) {
541 infof(data, "CONNECT responded chunked\n");
542 chunked_encoding = TRUE;
543 /* init our chunky engine */
544 Curl_httpchunk_init(conn);
545 }
546 else if(Curl_compareheader(line_start,
547 "Proxy-Connection:", "close"))
548 closeConnection = TRUE;
549 else if(2 == sscanf(line_start, "HTTP/1.%d %d",
550 &subversion,
551 &k->httpcode)) {
552 /* store the HTTP code from the proxy */
553 data->info.httpproxycode = k->httpcode;
554 }
555 /* put back the letter we blanked out before */
556 line_start[perline]= letter;
557
558 perline=0; /* line starts over here */
559 line_start = ptr+1; /* this skips the zero byte we wrote */
560 }
561 }
562 }
563 break;
564 } /* switch */
565 if(Curl_pgrsUpdate(conn))
566 return CURLE_ABORTED_BY_CALLBACK;
567 } /* while there's buffer left and loop is requested */
568
569 if(error)
570 return CURLE_RECV_ERROR;
571
572 if(data->info.httpproxycode != 200) {
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 */
584 closeConnection = TRUE;
585 }
586
587 if(closeConnection && data->req.newurl) {
588 /* Connection closed by server. Don't use it anymore */
589 Curl_closesocket(conn, conn->sock[sockindex]);
590 conn->sock[sockindex] = CURL_SOCKET_BAD;
591 break;
592 }
593 } /* END READING RESPONSE PHASE */
594
595 /* If we are supposed to continue and request a new URL, which basically
596 * means the HTTP authentication is still going on so if the tunnel
597 * is complete we start over in INIT state */
598 if(data->req.newurl &&
599 (TUNNEL_COMPLETE == conn->tunnel_state[sockindex])) {
600 conn->tunnel_state[sockindex] = TUNNEL_INIT;
601 infof(data, "TUNNEL_STATE switched to: %d\n",
602 conn->tunnel_state[sockindex]);
603 }
604
605 } while(data->req.newurl);
606
607 if(200 != data->req.httpcode) {
608 if(closeConnection && data->req.newurl) {
609 conn->bits.proxy_connect_closed = TRUE;
610 infof(data, "Connect me again please\n");
611 }
612 else {
613 free(data->req.newurl);
614 data->req.newurl = NULL;
615 /* failure, close this connection to avoid re-use */
616 streamclose(conn, "proxy CONNECT failure");
617 Curl_closesocket(conn, conn->sock[sockindex]);
618 conn->sock[sockindex] = CURL_SOCKET_BAD;
619 }
620
621 /* to back to init state */
622 conn->tunnel_state[sockindex] = TUNNEL_INIT;
623
624 if(conn->bits.proxy_connect_closed)
625 /* this is not an error, just part of the connection negotiation */
626 return CURLE_OK;
627 else {
628 failf(data, "Received HTTP code %d from proxy after CONNECT",
629 data->req.httpcode);
630 return CURLE_RECV_ERROR;
631 }
632 }
633
634 conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
635
636 /* If a proxy-authorization header was used for the proxy, then we should
637 make sure that it isn't accidentally used for the document request
638 after we've connected. So let's free and clear it here. */
639 Curl_safefree(conn->allocptr.proxyuserpwd);
640 conn->allocptr.proxyuserpwd = NULL;
641
642 data->state.authproxy.done = TRUE;
643
644 infof (data, "Proxy replied OK to CONNECT request\n");
645 data->req.ignorebody = FALSE; /* put it (back) to non-ignore state */
646 conn->bits.rewindaftersend = FALSE; /* make sure this isn't set for the
647 document request */
648 return CURLE_OK;
649 }
650 #endif /* CURL_DISABLE_PROXY */
651