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