• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #include "http_proxy.h"
26 
27 #if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP)
28 
29 #include <curl/curl.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  */
https_proxy_connect(struct connectdata * conn,int sockindex)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 
Curl_proxy_connect(struct connectdata * conn,int sockindex)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, remote_port);
126     conn->data->req.protop = prot_save;
127     if(CURLE_OK != result)
128       return result;
129     Curl_safefree(conn->allocptr.proxyuserpwd);
130 #else
131     return CURLE_NOT_BUILT_IN;
132 #endif
133   }
134   /* no HTTP tunnel proxy, just return */
135   return CURLE_OK;
136 }
137 
Curl_connect_complete(struct connectdata * conn)138 bool Curl_connect_complete(struct connectdata *conn)
139 {
140   return !conn->connect_state ||
141     (conn->connect_state->tunnel_state == TUNNEL_COMPLETE);
142 }
143 
Curl_connect_ongoing(struct connectdata * conn)144 bool Curl_connect_ongoing(struct connectdata *conn)
145 {
146   return conn->connect_state &&
147     (conn->connect_state->tunnel_state != TUNNEL_COMPLETE);
148 }
149 
connect_init(struct connectdata * conn,bool reinit)150 static CURLcode connect_init(struct connectdata *conn, bool reinit)
151 {
152   struct http_connect_state *s;
153   if(!reinit) {
154     DEBUGASSERT(!conn->connect_state);
155     s = calloc(1, sizeof(struct http_connect_state));
156     if(!s)
157       return CURLE_OUT_OF_MEMORY;
158     infof(conn->data, "allocate connect buffer!\n");
159     conn->connect_state = s;
160   }
161   else {
162     DEBUGASSERT(conn->connect_state);
163     s = conn->connect_state;
164   }
165   s->tunnel_state = TUNNEL_INIT;
166   s->keepon = TRUE;
167   s->line_start = s->connect_buffer;
168   s->ptr = s->line_start;
169   s->cl = 0;
170   s->close_connection = FALSE;
171   return CURLE_OK;
172 }
173 
connect_done(struct connectdata * conn)174 static void connect_done(struct connectdata *conn)
175 {
176   struct http_connect_state *s = conn->connect_state;
177   s->tunnel_state = TUNNEL_COMPLETE;
178   infof(conn->data, "CONNECT phase completed!\n");
179 }
180 
CONNECT(struct connectdata * conn,int sockindex,const char * hostname,int remote_port)181 static CURLcode CONNECT(struct connectdata *conn,
182                         int sockindex,
183                         const char *hostname,
184                         int remote_port)
185 {
186   int subversion = 0;
187   struct Curl_easy *data = conn->data;
188   struct SingleRequest *k = &data->req;
189   CURLcode result;
190   curl_socket_t tunnelsocket = conn->sock[sockindex];
191   timediff_t check;
192   struct http_connect_state *s = conn->connect_state;
193 
194 #define SELECT_OK      0
195 #define SELECT_ERROR   1
196 #define SELECT_TIMEOUT 2
197 
198   if(Curl_connect_complete(conn))
199     return CURLE_OK; /* CONNECT is already completed */
200 
201   conn->bits.proxy_connect_closed = FALSE;
202 
203   do {
204     if(TUNNEL_INIT == s->tunnel_state) {
205       /* BEGIN CONNECT PHASE */
206       char *host_port;
207       Curl_send_buffer *req_buffer;
208 
209       infof(data, "Establish HTTP proxy tunnel to %s:%hu\n",
210             hostname, remote_port);
211 
212         /* This only happens if we've looped here due to authentication
213            reasons, and we don't really use the newly cloned URL here
214            then. Just free() it. */
215       free(data->req.newurl);
216       data->req.newurl = NULL;
217 
218       /* initialize a dynamic send-buffer */
219       req_buffer = Curl_add_buffer_init();
220 
221       if(!req_buffer)
222         return CURLE_OUT_OF_MEMORY;
223 
224       host_port = aprintf("%s:%hu", hostname, remote_port);
225       if(!host_port) {
226         Curl_add_buffer_free(req_buffer);
227         return CURLE_OUT_OF_MEMORY;
228       }
229 
230       /* Setup the proxy-authorization header, if any */
231       result = Curl_http_output_auth(conn, "CONNECT", host_port, TRUE);
232 
233       free(host_port);
234 
235       if(!result) {
236         char *host = NULL;
237         const char *proxyconn = "";
238         const char *useragent = "";
239         const char *http = (conn->http_proxy.proxytype == CURLPROXY_HTTP_1_0) ?
240           "1.0" : "1.1";
241         bool ipv6_ip = conn->bits.ipv6_ip;
242         char *hostheader;
243 
244         /* the hostname may be different */
245         if(hostname != conn->host.name)
246           ipv6_ip = (strchr(hostname, ':') != NULL);
247         hostheader = /* host:port with IPv6 support */
248           aprintf("%s%s%s:%hu", ipv6_ip?"[":"", hostname, ipv6_ip?"]":"",
249                   remote_port);
250         if(!hostheader) {
251           Curl_add_buffer_free(req_buffer);
252           return CURLE_OUT_OF_MEMORY;
253         }
254 
255         if(!Curl_checkProxyheaders(conn, "Host:")) {
256           host = aprintf("Host: %s\r\n", hostheader);
257           if(!host) {
258             free(hostheader);
259             Curl_add_buffer_free(req_buffer);
260             return CURLE_OUT_OF_MEMORY;
261           }
262         }
263         if(!Curl_checkProxyheaders(conn, "Proxy-Connection:"))
264           proxyconn = "Proxy-Connection: Keep-Alive\r\n";
265 
266         if(!Curl_checkProxyheaders(conn, "User-Agent:") &&
267            data->set.str[STRING_USERAGENT])
268           useragent = conn->allocptr.uagent;
269 
270         result =
271           Curl_add_bufferf(req_buffer,
272                            "CONNECT %s HTTP/%s\r\n"
273                            "%s"  /* Host: */
274                            "%s"  /* Proxy-Authorization */
275                            "%s"  /* User-Agent */
276                            "%s", /* Proxy-Connection */
277                            hostheader,
278                            http,
279                            host?host:"",
280                            conn->allocptr.proxyuserpwd?
281                            conn->allocptr.proxyuserpwd:"",
282                            useragent,
283                            proxyconn);
284 
285         if(host)
286           free(host);
287         free(hostheader);
288 
289         if(!result)
290           result = Curl_add_custom_headers(conn, TRUE, req_buffer);
291 
292         if(!result)
293           /* CRLF terminate the request */
294           result = Curl_add_bufferf(req_buffer, "\r\n");
295 
296         if(!result) {
297           /* Send the connect request to the proxy */
298           /* BLOCKING */
299           result =
300             Curl_add_buffer_send(req_buffer, conn,
301                                  &data->info.request_size, 0, sockindex);
302         }
303         req_buffer = NULL;
304         if(result)
305           failf(data, "Failed sending CONNECT to proxy");
306       }
307 
308       Curl_add_buffer_free(req_buffer);
309       if(result)
310         return result;
311 
312       s->tunnel_state = TUNNEL_CONNECT;
313       s->perline = 0;
314     } /* END CONNECT PHASE */
315 
316     check = Curl_timeleft(data, NULL, TRUE);
317     if(check <= 0) {
318       failf(data, "Proxy CONNECT aborted due to timeout");
319       return CURLE_OPERATION_TIMEDOUT;
320     }
321 
322     if(!Curl_conn_data_pending(conn, sockindex))
323       /* return so we'll be called again polling-style */
324       return CURLE_OK;
325 
326     /* at this point, the tunnel_connecting phase is over. */
327 
328     { /* READING RESPONSE PHASE */
329       int error = SELECT_OK;
330 
331       while(s->keepon && !error) {
332         ssize_t gotbytes;
333 
334         /* make sure we have space to read more data */
335         if(s->ptr >= &s->connect_buffer[CONNECT_BUFFER_SIZE]) {
336           failf(data, "CONNECT response too large!");
337           return CURLE_RECV_ERROR;
338         }
339 
340         /* Read one byte at a time to avoid a race condition. Wait at most one
341            second before looping to ensure continuous pgrsUpdates. */
342         result = Curl_read(conn, tunnelsocket, s->ptr, 1, &gotbytes);
343         if(result == CURLE_AGAIN)
344           /* socket buffer drained, return */
345           return CURLE_OK;
346 
347         if(Curl_pgrsUpdate(conn))
348           return CURLE_ABORTED_BY_CALLBACK;
349 
350         if(result) {
351           s->keepon = FALSE;
352           break;
353         }
354         else if(gotbytes <= 0) {
355           if(data->set.proxyauth && data->state.authproxy.avail) {
356             /* proxy auth was requested and there was proxy auth available,
357                then deem this as "mere" proxy disconnect */
358             conn->bits.proxy_connect_closed = TRUE;
359             infof(data, "Proxy CONNECT connection closed\n");
360           }
361           else {
362             error = SELECT_ERROR;
363             failf(data, "Proxy CONNECT aborted");
364           }
365           s->keepon = FALSE;
366           break;
367         }
368 
369 
370         if(s->keepon > TRUE) {
371           /* This means we are currently ignoring a response-body */
372 
373           s->ptr = s->connect_buffer;
374           if(s->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             s->cl--;
378             if(s->cl <= 0) {
379               s->keepon = FALSE;
380               s->tunnel_state = TUNNEL_COMPLETE;
381               break;
382             }
383           }
384           else {
385             /* chunked-encoded body, so we need to do the chunked dance
386                properly to know when the end of the body is reached */
387             CHUNKcode r;
388             ssize_t tookcareof = 0;
389 
390             /* now parse the chunked piece of data so that we can
391                properly tell when the stream ends */
392             r = Curl_httpchunk_read(conn, s->ptr, 1, &tookcareof);
393             if(r == CHUNKE_STOP) {
394               /* we're done reading chunks! */
395               infof(data, "chunk reading DONE\n");
396               s->keepon = FALSE;
397               /* we did the full CONNECT treatment, go COMPLETE */
398               s->tunnel_state = TUNNEL_COMPLETE;
399             }
400           }
401           continue;
402         }
403 
404         s->perline++; /* amount of bytes in this line so far */
405 
406         /* if this is not the end of a header line then continue */
407         if(*s->ptr != 0x0a) {
408           s->ptr++;
409           continue;
410         }
411 
412         /* convert from the network encoding */
413         result = Curl_convert_from_network(data, s->line_start,
414                                            (size_t)s->perline);
415         /* Curl_convert_from_network calls failf if unsuccessful */
416         if(result)
417           return result;
418 
419         /* output debug if that is requested */
420         if(data->set.verbose)
421           Curl_debug(data, CURLINFO_HEADER_IN,
422                      s->line_start, (size_t)s->perline, conn);
423 
424         if(!data->set.suppress_connect_headers) {
425           /* send the header to the callback */
426           int writetype = CLIENTWRITE_HEADER;
427           if(data->set.include_header)
428             writetype |= CLIENTWRITE_BODY;
429 
430           result = Curl_client_write(conn, writetype,
431                                      s->line_start, s->perline);
432           if(result)
433             return result;
434         }
435 
436         data->info.header_size += (long)s->perline;
437         data->req.headerbytecount += (long)s->perline;
438 
439         /* Newlines are CRLF, so the CR is ignored as the line isn't
440            really terminated until the LF comes. Treat a following CR
441            as end-of-headers as well.*/
442 
443         if(('\r' == s->line_start[0]) ||
444            ('\n' == s->line_start[0])) {
445           /* end of response-headers from the proxy */
446           s->ptr = s->connect_buffer;
447           if((407 == k->httpcode) && !data->state.authproblem) {
448             /* If we get a 407 response code with content length
449                when we have no auth problem, we must ignore the
450                whole response-body */
451             s->keepon = 2;
452 
453             if(s->cl) {
454               infof(data, "Ignore %" CURL_FORMAT_CURL_OFF_T
455                     " bytes of response-body\n", s->cl);
456             }
457             else if(s->chunked_encoding) {
458               CHUNKcode r;
459 
460               infof(data, "Ignore chunked response-body\n");
461 
462               /* We set ignorebody true here since the chunked
463                  decoder function will acknowledge that. Pay
464                  attention so that this is cleared again when this
465                  function returns! */
466               k->ignorebody = TRUE;
467 
468               if(s->line_start[1] == '\n') {
469                 /* this can only be a LF if the letter at index 0
470                    was a CR */
471                 s->line_start++;
472               }
473 
474               /* now parse the chunked piece of data so that we can
475                  properly tell when the stream ends */
476               r = Curl_httpchunk_read(conn, s->line_start + 1, 1, &gotbytes);
477               if(r == CHUNKE_STOP) {
478                 /* we're done reading chunks! */
479                 infof(data, "chunk reading DONE\n");
480                 s->keepon = FALSE;
481                 /* we did the full CONNECT treatment, go to COMPLETE */
482                 s->tunnel_state = TUNNEL_COMPLETE;
483               }
484             }
485             else {
486               /* without content-length or chunked encoding, we
487                  can't keep the connection alive since the close is
488                  the end signal so we bail out at once instead */
489               s->keepon = FALSE;
490             }
491           }
492           else
493             s->keepon = FALSE;
494           if(!s->cl)
495             /* we did the full CONNECT treatment, go to COMPLETE */
496             s->tunnel_state = TUNNEL_COMPLETE;
497           continue;
498         }
499 
500         s->line_start[s->perline] = 0; /* zero terminate the buffer */
501         if((checkprefix("WWW-Authenticate:", s->line_start) &&
502             (401 == k->httpcode)) ||
503            (checkprefix("Proxy-authenticate:", s->line_start) &&
504             (407 == k->httpcode))) {
505 
506           bool proxy = (k->httpcode == 407) ? TRUE : FALSE;
507           char *auth = Curl_copy_header_value(s->line_start);
508           if(!auth)
509             return CURLE_OUT_OF_MEMORY;
510 
511           result = Curl_http_input_auth(conn, proxy, auth);
512 
513           free(auth);
514 
515           if(result)
516             return result;
517         }
518         else if(checkprefix("Content-Length:", s->line_start)) {
519           if(k->httpcode/100 == 2) {
520             /* A client MUST ignore any Content-Length or Transfer-Encoding
521                header fields received in a successful response to CONNECT.
522                "Successful" described as: 2xx (Successful). RFC 7231 4.3.6 */
523             infof(data, "Ignoring Content-Length in CONNECT %03d response\n",
524                   k->httpcode);
525           }
526           else {
527             (void)curlx_strtoofft(s->line_start +
528                                   strlen("Content-Length:"), NULL, 10, &s->cl);
529           }
530         }
531         else if(Curl_compareheader(s->line_start, "Connection:", "close"))
532           s->close_connection = TRUE;
533         else if(checkprefix("Transfer-Encoding:", s->line_start)) {
534           if(k->httpcode/100 == 2) {
535             /* A client MUST ignore any Content-Length or Transfer-Encoding
536                header fields received in a successful response to CONNECT.
537                "Successful" described as: 2xx (Successful). RFC 7231 4.3.6 */
538             infof(data, "Ignoring Transfer-Encoding in "
539                   "CONNECT %03d response\n", k->httpcode);
540           }
541           else if(Curl_compareheader(s->line_start,
542                                      "Transfer-Encoding:", "chunked")) {
543             infof(data, "CONNECT responded chunked\n");
544             s->chunked_encoding = TRUE;
545             /* init our chunky engine */
546             Curl_httpchunk_init(conn);
547           }
548         }
549         else if(Curl_compareheader(s->line_start,
550                                    "Proxy-Connection:", "close"))
551           s->close_connection = TRUE;
552         else if(2 == sscanf(s->line_start, "HTTP/1.%d %d",
553                             &subversion,
554                             &k->httpcode)) {
555           /* store the HTTP code from the proxy */
556           data->info.httpproxycode = k->httpcode;
557         }
558 
559         s->perline = 0; /* line starts over here */
560         s->ptr = s->connect_buffer;
561         s->line_start = s->ptr;
562       } /* while there's buffer left and loop is requested */
563 
564       if(Curl_pgrsUpdate(conn))
565         return CURLE_ABORTED_BY_CALLBACK;
566 
567       if(error)
568         return CURLE_RECV_ERROR;
569 
570       if(data->info.httpproxycode/100 != 2) {
571         /* Deal with the possibly already received authenticate
572            headers. 'newurl' is set to a new URL if we must loop. */
573         result = Curl_http_auth_act(conn);
574         if(result)
575           return result;
576 
577         if(conn->bits.close)
578           /* the connection has been marked for closure, most likely in the
579              Curl_http_auth_act() function and thus we can kill it at once
580              below */
581           s->close_connection = TRUE;
582       }
583 
584       if(s->close_connection && data->req.newurl) {
585         /* Connection closed by server. Don't use it anymore */
586         Curl_closesocket(conn, conn->sock[sockindex]);
587         conn->sock[sockindex] = CURL_SOCKET_BAD;
588         break;
589       }
590     } /* END READING RESPONSE PHASE */
591 
592     /* If we are supposed to continue and request a new URL, which basically
593      * means the HTTP authentication is still going on so if the tunnel
594      * is complete we start over in INIT state */
595     if(data->req.newurl && (TUNNEL_COMPLETE == s->tunnel_state)) {
596       connect_init(conn, TRUE); /* reinit */
597     }
598 
599   } while(data->req.newurl);
600 
601   if(data->info.httpproxycode/100 != 2) {
602     if(s->close_connection && data->req.newurl) {
603       conn->bits.proxy_connect_closed = TRUE;
604       infof(data, "Connect me again please\n");
605       connect_done(conn);
606     }
607     else {
608       free(data->req.newurl);
609       data->req.newurl = NULL;
610       /* failure, close this connection to avoid re-use */
611       streamclose(conn, "proxy CONNECT failure");
612       Curl_closesocket(conn, conn->sock[sockindex]);
613       conn->sock[sockindex] = CURL_SOCKET_BAD;
614     }
615 
616     /* to back to init state */
617     s->tunnel_state = TUNNEL_INIT;
618 
619     if(conn->bits.proxy_connect_closed)
620       /* this is not an error, just part of the connection negotiation */
621       return CURLE_OK;
622     failf(data, "Received HTTP code %d from proxy after CONNECT",
623           data->req.httpcode);
624     return CURLE_RECV_ERROR;
625   }
626 
627   s->tunnel_state = 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 %d to CONNECT request\n",
638         data->info.httpproxycode);
639   data->req.ignorebody = FALSE; /* put it (back) to non-ignore state */
640   conn->bits.rewindaftersend = FALSE; /* make sure this isn't set for the
641                                          document request  */
642   return CURLE_OK;
643 }
644 
Curl_connect_free(struct Curl_easy * data)645 void Curl_connect_free(struct Curl_easy *data)
646 {
647   struct connectdata *conn = data->easy_conn;
648   struct http_connect_state *s = conn->connect_state;
649   if(s) {
650     free(s);
651     conn->connect_state = NULL;
652   }
653 }
654 
655 /*
656  * Curl_proxyCONNECT() requires that we're connected to a HTTP proxy. This
657  * function will issue the necessary commands to get a seamless tunnel through
658  * this proxy. After that, the socket can be used just as a normal socket.
659  */
660 
Curl_proxyCONNECT(struct connectdata * conn,int sockindex,const char * hostname,int remote_port)661 CURLcode Curl_proxyCONNECT(struct connectdata *conn,
662                            int sockindex,
663                            const char *hostname,
664                            int remote_port)
665 {
666   CURLcode result;
667   if(!conn->connect_state) {
668     result = connect_init(conn, FALSE);
669     if(result)
670       return result;
671   }
672   result = CONNECT(conn, sockindex, hostname, remote_port);
673 
674   if(result || Curl_connect_complete(conn))
675     connect_done(conn);
676 
677   return result;
678 }
679 
680 #else
Curl_connect_free(struct Curl_easy * data)681 void Curl_connect_free(struct Curl_easy *data)
682 {
683   (void)data;
684 }
685 
686 #endif /* CURL_DISABLE_PROXY */
687