1/*************************************************************************** 2 * _ _ ____ _ 3 * Project ___| | | | _ \| | 4 * / __| | | | |_) | | 5 * | (__| |_| | _ <| |___ 6 * \___|\___/|_| \_\_____| 7 * 8 * Copyright (C) 1998 - 2011, 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 http://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 "setup.h" 24 25#if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP) 26 27#ifdef HAVE_UNISTD_H 28#include <unistd.h> 29#endif 30#include "urldata.h" 31#include <curl/curl.h> 32#include "http_proxy.h" 33#include "sendf.h" 34#include "http.h" 35#include "url.h" 36#include "select.h" 37#include "rawstr.h" 38#include "progress.h" 39#include "non-ascii.h" 40#include "connect.h" 41 42#define _MPRINTF_REPLACE /* use our functions only */ 43#include <curl/mprintf.h> 44 45#include "curlx.h" 46 47/* The last #include file should be: */ 48#include "memdebug.h" 49 50/* 51 * Curl_proxyCONNECT() requires that we're connected to a HTTP proxy. This 52 * function will issue the necessary commands to get a seamless tunnel through 53 * this proxy. After that, the socket can be used just as a normal socket. 54 * 55 * This badly needs to be rewritten. CONNECT should be sent and dealt with 56 * like any ordinary HTTP request, and not specially crafted like this. This 57 * function only remains here like this for now since the rewrite is a bit too 58 * much work to do at the moment. 59 * 60 * This function is BLOCKING which is nasty for all multi interface using apps. 61 */ 62 63CURLcode Curl_proxyCONNECT(struct connectdata *conn, 64 int sockindex, 65 const char *hostname, 66 unsigned short remote_port) 67{ 68 int subversion=0; 69 struct SessionHandle *data=conn->data; 70 struct SingleRequest *k = &data->req; 71 CURLcode result; 72 long timeout = 73 data->set.timeout?data->set.timeout:PROXY_TIMEOUT; /* in milliseconds */ 74 curl_socket_t tunnelsocket = conn->sock[sockindex]; 75 curl_off_t cl=0; 76 bool closeConnection = FALSE; 77 bool chunked_encoding = FALSE; 78 long check; 79 80#define SELECT_OK 0 81#define SELECT_ERROR 1 82#define SELECT_TIMEOUT 2 83 int error = SELECT_OK; 84 85 conn->bits.proxy_connect_closed = FALSE; 86 87 do { 88 if(!conn->bits.tunnel_connecting) { /* BEGIN CONNECT PHASE */ 89 char *host_port; 90 Curl_send_buffer *req_buffer; 91 92 infof(data, "Establish HTTP proxy tunnel to %s:%hu\n", 93 hostname, remote_port); 94 95 if(data->req.newurl) { 96 /* This only happens if we've looped here due to authentication 97 reasons, and we don't really use the newly cloned URL here 98 then. Just free() it. */ 99 free(data->req.newurl); 100 data->req.newurl = NULL; 101 } 102 103 /* initialize a dynamic send-buffer */ 104 req_buffer = Curl_add_buffer_init(); 105 106 if(!req_buffer) 107 return CURLE_OUT_OF_MEMORY; 108 109 host_port = aprintf("%s:%hu", hostname, remote_port); 110 if(!host_port) { 111 free(req_buffer); 112 return CURLE_OUT_OF_MEMORY; 113 } 114 115 /* Setup the proxy-authorization header, if any */ 116 result = Curl_http_output_auth(conn, "CONNECT", host_port, TRUE); 117 118 if(CURLE_OK == result) { 119 char *host=(char *)""; 120 const char *proxyconn=""; 121 const char *useragent=""; 122 const char *http = (conn->proxytype == CURLPROXY_HTTP_1_0) ? 123 "1.0" : "1.1"; 124 125 if(!Curl_checkheaders(data, "Host:")) { 126 host = aprintf("Host: %s\r\n", host_port); 127 if(!host) { 128 free(req_buffer); 129 free(host_port); 130 return CURLE_OUT_OF_MEMORY; 131 } 132 } 133 if(!Curl_checkheaders(data, "Proxy-Connection:")) 134 proxyconn = "Proxy-Connection: Keep-Alive\r\n"; 135 136 if(!Curl_checkheaders(data, "User-Agent:") && 137 data->set.str[STRING_USERAGENT]) 138 useragent = conn->allocptr.uagent; 139 140 /* Send the connect request to the proxy */ 141 /* BLOCKING */ 142 result = 143 Curl_add_bufferf(req_buffer, 144 "CONNECT %s:%hu HTTP/%s\r\n" 145 "%s" /* Host: */ 146 "%s" /* Proxy-Authorization */ 147 "%s" /* User-Agent */ 148 "%s", /* Proxy-Connection */ 149 hostname, remote_port, http, 150 host, 151 conn->allocptr.proxyuserpwd? 152 conn->allocptr.proxyuserpwd:"", 153 useragent, 154 proxyconn); 155 156 if(host && *host) 157 free(host); 158 159 if(CURLE_OK == result) 160 result = Curl_add_custom_headers(conn, req_buffer); 161 162 if(CURLE_OK == result) 163 /* CRLF terminate the request */ 164 result = Curl_add_bufferf(req_buffer, "\r\n"); 165 166 if(CURLE_OK == result) { 167 /* Now send off the request */ 168 result = 169 Curl_add_buffer_send(req_buffer, conn, 170 &data->info.request_size, 0, sockindex); 171 } 172 req_buffer = NULL; 173 if(result) 174 failf(data, "Failed sending CONNECT to proxy"); 175 } 176 free(host_port); 177 Curl_safefree(req_buffer); 178 if(result) 179 return result; 180 181 conn->bits.tunnel_connecting = TRUE; 182 } /* END CONNECT PHASE */ 183 184 /* now we've issued the CONNECT and we're waiting to hear back - 185 we try not to block here in multi-mode because that might be a LONG 186 wait if the proxy cannot connect-through to the remote host. */ 187 188 /* if timeout is requested, find out how much remaining time we have */ 189 check = timeout - /* timeout time */ 190 Curl_tvdiff(Curl_tvnow(), conn->now); /* spent time */ 191 if(check <= 0) { 192 failf(data, "Proxy CONNECT aborted due to timeout"); 193 return CURLE_RECV_ERROR; 194 } 195 196 /* if we're in multi-mode and we would block, return instead for a retry */ 197 if(Curl_if_multi == data->state.used_interface) { 198 if(0 == Curl_socket_ready(tunnelsocket, CURL_SOCKET_BAD, 0)) 199 /* return so we'll be called again polling-style */ 200 return CURLE_OK; 201 else { 202 DEBUGF(infof(data, 203 "Multi mode finished polling for response from " 204 "proxy CONNECT.")); 205 } 206 } 207 else { 208 DEBUGF(infof(data, "Easy mode waiting response from proxy CONNECT.")); 209 } 210 211 /* at this point, either: 212 1) we're in easy-mode and so it's okay to block waiting for a CONNECT 213 response 214 2) we're in multi-mode and we didn't block - it's either an error or we 215 now have some data waiting. 216 In any case, the tunnel_connecting phase is over. */ 217 conn->bits.tunnel_connecting = FALSE; 218 219 { /* BEGIN NEGOTIATION PHASE */ 220 size_t nread; /* total size read */ 221 int perline; /* count bytes per line */ 222 int keepon=TRUE; 223 ssize_t gotbytes; 224 char *ptr; 225 char *line_start; 226 227 ptr=data->state.buffer; 228 line_start = ptr; 229 230 nread=0; 231 perline=0; 232 keepon=TRUE; 233 234 while((nread<BUFSIZE) && (keepon && !error)) { 235 236 /* if timeout is requested, find out how much remaining time we have */ 237 check = timeout - /* timeout time */ 238 Curl_tvdiff(Curl_tvnow(), conn->now); /* spent time */ 239 if(check <= 0) { 240 failf(data, "Proxy CONNECT aborted due to timeout"); 241 error = SELECT_TIMEOUT; /* already too little time */ 242 break; 243 } 244 245 /* loop every second at least, less if the timeout is near */ 246 switch (Curl_socket_ready(tunnelsocket, CURL_SOCKET_BAD, 247 check<1000L?check:1000)) { 248 case -1: /* select() error, stop reading */ 249 error = SELECT_ERROR; 250 failf(data, "Proxy CONNECT aborted due to select/poll error"); 251 break; 252 case 0: /* timeout */ 253 break; 254 default: 255 DEBUGASSERT(ptr+BUFSIZE-nread <= data->state.buffer+BUFSIZE+1); 256 result = Curl_read(conn, tunnelsocket, ptr, BUFSIZE-nread, 257 &gotbytes); 258 if(result==CURLE_AGAIN) 259 continue; /* go loop yourself */ 260 else if(result) 261 keepon = FALSE; 262 else if(gotbytes <= 0) { 263 keepon = FALSE; 264 if(data->set.proxyauth && data->state.authproxy.avail) { 265 /* proxy auth was requested and there was proxy auth available, 266 then deem this as "mere" proxy disconnect */ 267 conn->bits.proxy_connect_closed = TRUE; 268 } 269 else { 270 error = SELECT_ERROR; 271 failf(data, "Proxy CONNECT aborted"); 272 } 273 } 274 else { 275 /* 276 * We got a whole chunk of data, which can be anything from one 277 * byte to a set of lines and possibly just a piece of the last 278 * line. 279 */ 280 int i; 281 282 nread += gotbytes; 283 284 if(keepon > TRUE) { 285 /* This means we are currently ignoring a response-body */ 286 287 nread = 0; /* make next read start over in the read buffer */ 288 ptr=data->state.buffer; 289 if(cl) { 290 /* A Content-Length based body: simply count down the counter 291 and make sure to break out of the loop when we're done! */ 292 cl -= gotbytes; 293 if(cl<=0) { 294 keepon = FALSE; 295 break; 296 } 297 } 298 else { 299 /* chunked-encoded body, so we need to do the chunked dance 300 properly to know when the end of the body is reached */ 301 CHUNKcode r; 302 ssize_t tookcareof=0; 303 304 /* now parse the chunked piece of data so that we can 305 properly tell when the stream ends */ 306 r = Curl_httpchunk_read(conn, ptr, gotbytes, &tookcareof); 307 if(r == CHUNKE_STOP) { 308 /* we're done reading chunks! */ 309 infof(data, "chunk reading DONE\n"); 310 keepon = FALSE; 311 } 312 else 313 infof(data, "Read %zd bytes of chunk, continue\n", 314 tookcareof); 315 } 316 } 317 else 318 for(i = 0; i < gotbytes; ptr++, i++) { 319 perline++; /* amount of bytes in this line so far */ 320 if(*ptr == 0x0a) { 321 char letter; 322 int writetype; 323 324 /* convert from the network encoding */ 325 result = Curl_convert_from_network(data, line_start, 326 perline); 327 /* Curl_convert_from_network calls failf if unsuccessful */ 328 if(result) 329 return result; 330 331 /* output debug if that is requested */ 332 if(data->set.verbose) 333 Curl_debug(data, CURLINFO_HEADER_IN, 334 line_start, (size_t)perline, conn); 335 336 /* send the header to the callback */ 337 writetype = CLIENTWRITE_HEADER; 338 if(data->set.include_header) 339 writetype |= CLIENTWRITE_BODY; 340 341 result = Curl_client_write(conn, writetype, line_start, 342 perline); 343 if(result) 344 return result; 345 346 /* Newlines are CRLF, so the CR is ignored as the line isn't 347 really terminated until the LF comes. Treat a following CR 348 as end-of-headers as well.*/ 349 350 if(('\r' == line_start[0]) || 351 ('\n' == line_start[0])) { 352 /* end of response-headers from the proxy */ 353 nread = 0; /* make next read start over in the read 354 buffer */ 355 ptr=data->state.buffer; 356 if((407 == k->httpcode) && !data->state.authproblem) { 357 /* If we get a 407 response code with content length 358 when we have no auth problem, we must ignore the 359 whole response-body */ 360 keepon = 2; 361 362 if(cl) { 363 364 infof(data, "Ignore %" FORMAT_OFF_T 365 " bytes of response-body\n", cl); 366 /* remove the remaining chunk of what we already 367 read */ 368 cl -= (gotbytes - i); 369 370 if(cl<=0) 371 /* if the whole thing was already read, we are done! 372 */ 373 keepon=FALSE; 374 } 375 else if(chunked_encoding) { 376 CHUNKcode r; 377 /* We set ignorebody true here since the chunked 378 decoder function will acknowledge that. Pay 379 attention so that this is cleared again when this 380 function returns! */ 381 k->ignorebody = TRUE; 382 infof(data, "%zd bytes of chunk left\n", gotbytes-i); 383 384 if(line_start[1] == '\n') { 385 /* this can only be a LF if the letter at index 0 386 was a CR */ 387 line_start++; 388 i++; 389 } 390 391 /* now parse the chunked piece of data so that we can 392 properly tell when the stream ends */ 393 r = Curl_httpchunk_read(conn, line_start+1, 394 gotbytes -i, &gotbytes); 395 if(r == CHUNKE_STOP) { 396 /* we're done reading chunks! */ 397 infof(data, "chunk reading DONE\n"); 398 keepon = FALSE; 399 } 400 else 401 infof(data, "Read %zd bytes of chunk, continue\n", 402 gotbytes); 403 } 404 else { 405 /* without content-length or chunked encoding, we 406 can't keep the connection alive since the close is 407 the end signal so we bail out at once instead */ 408 keepon=FALSE; 409 } 410 } 411 else 412 keepon = FALSE; 413 break; /* breaks out of for-loop, not switch() */ 414 } 415 416 /* keep a backup of the position we are about to blank */ 417 letter = line_start[perline]; 418 line_start[perline]=0; /* zero terminate the buffer */ 419 if((checkprefix("WWW-Authenticate:", line_start) && 420 (401 == k->httpcode)) || 421 (checkprefix("Proxy-authenticate:", line_start) && 422 (407 == k->httpcode))) { 423 result = Curl_http_input_auth(conn, k->httpcode, 424 line_start); 425 if(result) 426 return result; 427 } 428 else if(checkprefix("Content-Length:", line_start)) { 429 cl = curlx_strtoofft(line_start + 430 strlen("Content-Length:"), NULL, 10); 431 } 432 else if(Curl_compareheader(line_start, 433 "Connection:", "close")) 434 closeConnection = TRUE; 435 else if(Curl_compareheader(line_start, 436 "Transfer-Encoding:", 437 "chunked")) { 438 infof(data, "CONNECT responded chunked\n"); 439 chunked_encoding = TRUE; 440 /* init our chunky engine */ 441 Curl_httpchunk_init(conn); 442 } 443 else if(Curl_compareheader(line_start, 444 "Proxy-Connection:", "close")) 445 closeConnection = TRUE; 446 else if(2 == sscanf(line_start, "HTTP/1.%d %d", 447 &subversion, 448 &k->httpcode)) { 449 /* store the HTTP code from the proxy */ 450 data->info.httpproxycode = k->httpcode; 451 } 452 /* put back the letter we blanked out before */ 453 line_start[perline]= letter; 454 455 perline=0; /* line starts over here */ 456 line_start = ptr+1; /* this skips the zero byte we wrote */ 457 } 458 } 459 } 460 break; 461 } /* switch */ 462 if(Curl_pgrsUpdate(conn)) 463 return CURLE_ABORTED_BY_CALLBACK; 464 } /* while there's buffer left and loop is requested */ 465 466 if(error) 467 return CURLE_RECV_ERROR; 468 469 if(data->info.httpproxycode != 200) { 470 /* Deal with the possibly already received authenticate 471 headers. 'newurl' is set to a new URL if we must loop. */ 472 result = Curl_http_auth_act(conn); 473 if(result) 474 return result; 475 476 if(conn->bits.close) 477 /* the connection has been marked for closure, most likely in the 478 Curl_http_auth_act() function and thus we can kill it at once 479 below 480 */ 481 closeConnection = TRUE; 482 } 483 484 if(closeConnection && data->req.newurl) { 485 /* Connection closed by server. Don't use it anymore */ 486 Curl_closesocket(conn, conn->sock[sockindex]); 487 conn->sock[sockindex] = CURL_SOCKET_BAD; 488 break; 489 } 490 } /* END NEGOTIATION PHASE */ 491 } while(data->req.newurl); 492 493 if(200 != data->req.httpcode) { 494 failf(data, "Received HTTP code %d from proxy after CONNECT", 495 data->req.httpcode); 496 497 if(closeConnection && data->req.newurl) 498 conn->bits.proxy_connect_closed = TRUE; 499 500 return CURLE_RECV_ERROR; 501 } 502 503 /* If a proxy-authorization header was used for the proxy, then we should 504 make sure that it isn't accidentally used for the document request 505 after we've connected. So let's free and clear it here. */ 506 Curl_safefree(conn->allocptr.proxyuserpwd); 507 conn->allocptr.proxyuserpwd = NULL; 508 509 data->state.authproxy.done = TRUE; 510 511 infof (data, "Proxy replied OK to CONNECT request\n"); 512 data->req.ignorebody = FALSE; /* put it (back) to non-ignore state */ 513 return CURLE_OK; 514} 515#endif /* CURL_DISABLE_PROXY */ 516