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