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