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