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