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