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