xref: /third_party/curl/lib/socks.c (revision 13498266)
1/***************************************************************************
2 *                                  _   _ ____  _
3 *  Project                     ___| | | |  _ \| |
4 *                             / __| | | | |_) | |
5 *                            | (__| |_| |  _ <| |___
6 *                             \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 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.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 * SPDX-License-Identifier: curl
22 *
23 ***************************************************************************/
24
25#include "curl_setup.h"
26
27#if !defined(CURL_DISABLE_PROXY)
28
29#ifdef HAVE_NETINET_IN_H
30#include <netinet/in.h>
31#endif
32#ifdef HAVE_ARPA_INET_H
33#include <arpa/inet.h>
34#endif
35
36#include "urldata.h"
37#include "sendf.h"
38#include "select.h"
39#include "cfilters.h"
40#include "connect.h"
41#include "timeval.h"
42#include "socks.h"
43#include "multiif.h" /* for getsock macros */
44#include "inet_pton.h"
45#include "url.h"
46
47/* The last 3 #include files should be in this order */
48#include "curl_printf.h"
49#include "curl_memory.h"
50#include "memdebug.h"
51
52/* for the (SOCKS) connect state machine */
53enum connect_t {
54  CONNECT_INIT,
55  CONNECT_SOCKS_INIT, /* 1 */
56  CONNECT_SOCKS_SEND, /* 2 waiting to send more first data */
57  CONNECT_SOCKS_READ_INIT, /* 3 set up read */
58  CONNECT_SOCKS_READ, /* 4 read server response */
59  CONNECT_GSSAPI_INIT, /* 5 */
60  CONNECT_AUTH_INIT, /* 6 setup outgoing auth buffer */
61  CONNECT_AUTH_SEND, /* 7 send auth */
62  CONNECT_AUTH_READ, /* 8 read auth response */
63  CONNECT_REQ_INIT,  /* 9 init SOCKS "request" */
64  CONNECT_RESOLVING, /* 10 */
65  CONNECT_RESOLVED,  /* 11 */
66  CONNECT_RESOLVE_REMOTE, /* 12 */
67  CONNECT_REQ_SEND,  /* 13 */
68  CONNECT_REQ_SENDING, /* 14 */
69  CONNECT_REQ_READ,  /* 15 */
70  CONNECT_REQ_READ_MORE, /* 16 */
71  CONNECT_DONE /* 17 connected fine to the remote or the SOCKS proxy */
72};
73
74#define CURL_SOCKS_BUF_SIZE 600
75
76/* make sure we configure it not too low */
77#if CURL_SOCKS_BUF_SIZE < 600
78#error CURL_SOCKS_BUF_SIZE must be at least 600
79#endif
80
81
82struct socks_state {
83  enum connect_t state;
84  ssize_t outstanding;  /* send this many bytes more */
85  unsigned char buffer[CURL_SOCKS_BUF_SIZE];
86  unsigned char *outp; /* send from this pointer */
87
88  const char *hostname;
89  int remote_port;
90  const char *proxy_user;
91  const char *proxy_password;
92};
93
94#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
95/*
96 * Helper read-from-socket functions. Does the same as Curl_read() but it
97 * blocks until all bytes amount of buffersize will be read. No more, no less.
98 *
99 * This is STUPID BLOCKING behavior. Only used by the SOCKS GSSAPI functions.
100 */
101int Curl_blockread_all(struct Curl_cfilter *cf,
102                       struct Curl_easy *data,   /* transfer */
103                       char *buf,                /* store read data here */
104                       ssize_t buffersize,       /* max amount to read */
105                       ssize_t *n)               /* amount bytes read */
106{
107  ssize_t nread = 0;
108  ssize_t allread = 0;
109  int result;
110  CURLcode err = CURLE_OK;
111
112  *n = 0;
113  for(;;) {
114    timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE);
115    if(timeout_ms < 0) {
116      /* we already got the timeout */
117      result = CURLE_OPERATION_TIMEDOUT;
118      break;
119    }
120    if(!timeout_ms)
121      timeout_ms = TIMEDIFF_T_MAX;
122    if(SOCKET_READABLE(cf->conn->sock[cf->sockindex], timeout_ms) <= 0) {
123      result = ~CURLE_OK;
124      break;
125    }
126    nread = Curl_conn_cf_recv(cf->next, data, buf, buffersize, &err);
127    if(nread <= 0) {
128      result = err;
129      if(CURLE_AGAIN == err)
130        continue;
131      if(err) {
132        break;
133      }
134    }
135
136    if(buffersize == nread) {
137      allread += nread;
138      *n = allread;
139      result = CURLE_OK;
140      break;
141    }
142    if(!nread) {
143      result = ~CURLE_OK;
144      break;
145    }
146
147    buffersize -= nread;
148    buf += nread;
149    allread += nread;
150  }
151  return result;
152}
153#endif
154
155#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
156#define DEBUG_AND_VERBOSE
157#define sxstate(x,d,y) socksstate(x,d,y, __LINE__)
158#else
159#define sxstate(x,d,y) socksstate(x,d,y)
160#endif
161
162/* always use this function to change state, to make debugging easier */
163static void socksstate(struct socks_state *sx, struct Curl_easy *data,
164                       enum connect_t state
165#ifdef DEBUG_AND_VERBOSE
166                       , int lineno
167#endif
168)
169{
170  enum connect_t oldstate = sx->state;
171#ifdef DEBUG_AND_VERBOSE
172  /* synced with the state list in urldata.h */
173  static const char * const socks_statename[] = {
174    "INIT",
175    "SOCKS_INIT",
176    "SOCKS_SEND",
177    "SOCKS_READ_INIT",
178    "SOCKS_READ",
179    "GSSAPI_INIT",
180    "AUTH_INIT",
181    "AUTH_SEND",
182    "AUTH_READ",
183    "REQ_INIT",
184    "RESOLVING",
185    "RESOLVED",
186    "RESOLVE_REMOTE",
187    "REQ_SEND",
188    "REQ_SENDING",
189    "REQ_READ",
190    "REQ_READ_MORE",
191    "DONE"
192  };
193#endif
194
195  (void)data;
196  if(oldstate == state)
197    /* don't bother when the new state is the same as the old state */
198    return;
199
200  sx->state = state;
201
202#ifdef DEBUG_AND_VERBOSE
203  infof(data,
204        "SXSTATE: %s => %s; line %d",
205        socks_statename[oldstate], socks_statename[sx->state],
206        lineno);
207#endif
208}
209
210static CURLproxycode socks_state_send(struct Curl_cfilter *cf,
211                                      struct socks_state *sx,
212                                      struct Curl_easy *data,
213                                      CURLproxycode failcode,
214                                      const char *description)
215{
216  ssize_t nwritten;
217  CURLcode result;
218
219  nwritten = Curl_conn_cf_send(cf->next, data, (char *)sx->outp,
220                               sx->outstanding, &result);
221  if(nwritten <= 0) {
222    if(CURLE_AGAIN == result) {
223      return CURLPX_OK;
224    }
225    else if(CURLE_OK == result) {
226      /* connection closed */
227      failf(data, "connection to proxy closed");
228      return CURLPX_CLOSED;
229    }
230    failf(data, "Failed to send %s: %s", description,
231          curl_easy_strerror(result));
232    return failcode;
233  }
234  DEBUGASSERT(sx->outstanding >= nwritten);
235  /* not done, remain in state */
236  sx->outstanding -= nwritten;
237  sx->outp += nwritten;
238  return CURLPX_OK;
239}
240
241static CURLproxycode socks_state_recv(struct Curl_cfilter *cf,
242                                      struct socks_state *sx,
243                                      struct Curl_easy *data,
244                                      CURLproxycode failcode,
245                                      const char *description)
246{
247  ssize_t nread;
248  CURLcode result;
249
250  nread = Curl_conn_cf_recv(cf->next, data, (char *)sx->outp,
251                            sx->outstanding, &result);
252  if(nread <= 0) {
253    if(CURLE_AGAIN == result) {
254      return CURLPX_OK;
255    }
256    else if(CURLE_OK == result) {
257      /* connection closed */
258      failf(data, "connection to proxy closed");
259      return CURLPX_CLOSED;
260    }
261    failf(data, "SOCKS: Failed receiving %s: %s", description,
262          curl_easy_strerror(result));
263    return failcode;
264  }
265  /* remain in reading state */
266  DEBUGASSERT(sx->outstanding >= nread);
267  sx->outstanding -= nread;
268  sx->outp += nread;
269  return CURLPX_OK;
270}
271
272/*
273* This function logs in to a SOCKS4 proxy and sends the specifics to the final
274* destination server.
275*
276* Reference :
277*   https://www.openssh.com/txt/socks4.protocol
278*
279* Note :
280*   Set protocol4a=true for  "SOCKS 4A (Simple Extension to SOCKS 4 Protocol)"
281*   Nonsupport "Identification Protocol (RFC1413)"
282*/
283static CURLproxycode do_SOCKS4(struct Curl_cfilter *cf,
284                               struct socks_state *sx,
285                               struct Curl_easy *data)
286{
287  struct connectdata *conn = cf->conn;
288  const bool protocol4a =
289    (conn->socks_proxy.proxytype == CURLPROXY_SOCKS4A) ? TRUE : FALSE;
290  unsigned char *socksreq = sx->buffer;
291  CURLcode result;
292  CURLproxycode presult;
293  struct Curl_dns_entry *dns = NULL;
294
295  switch(sx->state) {
296  case CONNECT_SOCKS_INIT:
297    /* SOCKS4 can only do IPv4, insist! */
298    conn->ip_version = CURL_IPRESOLVE_V4;
299    if(conn->bits.httpproxy)
300      infof(data, "SOCKS4%s: connecting to HTTP proxy %s port %d",
301            protocol4a ? "a" : "", sx->hostname, sx->remote_port);
302
303    infof(data, "SOCKS4 communication to %s:%d",
304          sx->hostname, sx->remote_port);
305
306    /*
307     * Compose socks4 request
308     *
309     * Request format
310     *
311     *     +----+----+----+----+----+----+----+----+----+----+....+----+
312     *     | VN | CD | DSTPORT |      DSTIP        | USERID       |NULL|
313     *     +----+----+----+----+----+----+----+----+----+----+....+----+
314     * # of bytes:  1    1      2              4           variable       1
315     */
316
317    socksreq[0] = 4; /* version (SOCKS4) */
318    socksreq[1] = 1; /* connect */
319    socksreq[2] = (unsigned char)((sx->remote_port >> 8) & 0xff); /* MSB */
320    socksreq[3] = (unsigned char)(sx->remote_port & 0xff);        /* LSB */
321
322    /* DNS resolve only for SOCKS4, not SOCKS4a */
323    if(!protocol4a) {
324      enum resolve_t rc =
325        Curl_resolv(data, sx->hostname, sx->remote_port, TRUE, &dns);
326
327      if(rc == CURLRESOLV_ERROR)
328        return CURLPX_RESOLVE_HOST;
329      else if(rc == CURLRESOLV_PENDING) {
330        sxstate(sx, data, CONNECT_RESOLVING);
331        infof(data, "SOCKS4 non-blocking resolve of %s", sx->hostname);
332        return CURLPX_OK;
333      }
334      sxstate(sx, data, CONNECT_RESOLVED);
335      goto CONNECT_RESOLVED;
336    }
337
338    /* socks4a doesn't resolve anything locally */
339    sxstate(sx, data, CONNECT_REQ_INIT);
340    goto CONNECT_REQ_INIT;
341
342  case CONNECT_RESOLVING:
343    /* check if we have the name resolved by now */
344    dns = Curl_fetch_addr(data, sx->hostname, (int)conn->port);
345
346    if(dns) {
347#ifdef CURLRES_ASYNCH
348      data->state.async.dns = dns;
349      data->state.async.done = TRUE;
350#endif
351      infof(data, "Hostname '%s' was found", sx->hostname);
352      sxstate(sx, data, CONNECT_RESOLVED);
353    }
354    else {
355      result = Curl_resolv_check(data, &dns);
356      if(!dns) {
357        if(result)
358          return CURLPX_RESOLVE_HOST;
359        return CURLPX_OK;
360      }
361    }
362    FALLTHROUGH();
363  case CONNECT_RESOLVED:
364CONNECT_RESOLVED:
365  {
366    struct Curl_addrinfo *hp = NULL;
367    /*
368     * We cannot use 'hostent' as a struct that Curl_resolv() returns.  It
369     * returns a Curl_addrinfo pointer that may not always look the same.
370     */
371    if(dns) {
372      hp = dns->addr;
373
374      /* scan for the first IPv4 address */
375      while(hp && (hp->ai_family != AF_INET))
376        hp = hp->ai_next;
377
378      if(hp) {
379        struct sockaddr_in *saddr_in;
380        char buf[64];
381        Curl_printable_address(hp, buf, sizeof(buf));
382
383        saddr_in = (struct sockaddr_in *)(void *)hp->ai_addr;
384        socksreq[4] = ((unsigned char *)&saddr_in->sin_addr.s_addr)[0];
385        socksreq[5] = ((unsigned char *)&saddr_in->sin_addr.s_addr)[1];
386        socksreq[6] = ((unsigned char *)&saddr_in->sin_addr.s_addr)[2];
387        socksreq[7] = ((unsigned char *)&saddr_in->sin_addr.s_addr)[3];
388
389        infof(data, "SOCKS4 connect to IPv4 %s (locally resolved)", buf);
390
391        Curl_resolv_unlock(data, dns); /* not used anymore from now on */
392      }
393      else
394        failf(data, "SOCKS4 connection to %s not supported", sx->hostname);
395    }
396    else
397      failf(data, "Failed to resolve \"%s\" for SOCKS4 connect.",
398            sx->hostname);
399
400    if(!hp)
401      return CURLPX_RESOLVE_HOST;
402  }
403    FALLTHROUGH();
404  case CONNECT_REQ_INIT:
405CONNECT_REQ_INIT:
406    /*
407     * This is currently not supporting "Identification Protocol (RFC1413)".
408     */
409    socksreq[8] = 0; /* ensure empty userid is NUL-terminated */
410    if(sx->proxy_user) {
411      size_t plen = strlen(sx->proxy_user);
412      if(plen > 255) {
413        /* there is no real size limit to this field in the protocol, but
414           SOCKS5 limits the proxy user field to 255 bytes and it seems likely
415           that a longer field is either a mistake or malicious input */
416        failf(data, "Too long SOCKS proxy user name");
417        return CURLPX_LONG_USER;
418      }
419      /* copy the proxy name WITH trailing zero */
420      memcpy(socksreq + 8, sx->proxy_user, plen + 1);
421    }
422
423    /*
424     * Make connection
425     */
426    {
427      size_t packetsize = 9 +
428        strlen((char *)socksreq + 8); /* size including NUL */
429
430      /* If SOCKS4a, set special invalid IP address 0.0.0.x */
431      if(protocol4a) {
432        size_t hostnamelen = 0;
433        socksreq[4] = 0;
434        socksreq[5] = 0;
435        socksreq[6] = 0;
436        socksreq[7] = 1;
437        /* append hostname */
438        hostnamelen = strlen(sx->hostname) + 1; /* length including NUL */
439        if((hostnamelen <= 255) &&
440           (packetsize + hostnamelen < sizeof(sx->buffer)))
441          strcpy((char *)socksreq + packetsize, sx->hostname);
442        else {
443          failf(data, "SOCKS4: too long host name");
444          return CURLPX_LONG_HOSTNAME;
445        }
446        packetsize += hostnamelen;
447      }
448      sx->outp = socksreq;
449      DEBUGASSERT(packetsize <= sizeof(sx->buffer));
450      sx->outstanding = packetsize;
451      sxstate(sx, data, CONNECT_REQ_SENDING);
452    }
453    FALLTHROUGH();
454  case CONNECT_REQ_SENDING:
455    /* Send request */
456    presult = socks_state_send(cf, sx, data, CURLPX_SEND_CONNECT,
457                               "SOCKS4 connect request");
458    if(CURLPX_OK != presult)
459      return presult;
460    else if(sx->outstanding) {
461      /* remain in sending state */
462      return CURLPX_OK;
463    }
464    /* done sending! */
465    sx->outstanding = 8; /* receive data size */
466    sx->outp = socksreq;
467    sxstate(sx, data, CONNECT_SOCKS_READ);
468
469    FALLTHROUGH();
470  case CONNECT_SOCKS_READ:
471    /* Receive response */
472    presult = socks_state_recv(cf, sx, data, CURLPX_RECV_CONNECT,
473                               "connect request ack");
474    if(CURLPX_OK != presult)
475      return presult;
476    else if(sx->outstanding) {
477      /* remain in reading state */
478      return CURLPX_OK;
479    }
480    sxstate(sx, data, CONNECT_DONE);
481    break;
482  default: /* lots of unused states in SOCKS4 */
483    break;
484  }
485
486  /*
487   * Response format
488   *
489   *     +----+----+----+----+----+----+----+----+
490   *     | VN | CD | DSTPORT |      DSTIP        |
491   *     +----+----+----+----+----+----+----+----+
492   * # of bytes:  1    1      2              4
493   *
494   * VN is the version of the reply code and should be 0. CD is the result
495   * code with one of the following values:
496   *
497   * 90: request granted
498   * 91: request rejected or failed
499   * 92: request rejected because SOCKS server cannot connect to
500   *     identd on the client
501   * 93: request rejected because the client program and identd
502   *     report different user-ids
503   */
504
505  /* wrong version ? */
506  if(socksreq[0]) {
507    failf(data,
508          "SOCKS4 reply has wrong version, version should be 0.");
509    return CURLPX_BAD_VERSION;
510  }
511
512  /* Result */
513  switch(socksreq[1]) {
514  case 90:
515    infof(data, "SOCKS4%s request granted.", protocol4a?"a":"");
516    break;
517  case 91:
518    failf(data,
519          "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)"
520          ", request rejected or failed.",
521          socksreq[4], socksreq[5], socksreq[6], socksreq[7],
522          (((unsigned char)socksreq[2] << 8) | (unsigned char)socksreq[3]),
523          (unsigned char)socksreq[1]);
524    return CURLPX_REQUEST_FAILED;
525  case 92:
526    failf(data,
527          "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)"
528          ", request rejected because SOCKS server cannot connect to "
529          "identd on the client.",
530          socksreq[4], socksreq[5], socksreq[6], socksreq[7],
531          (((unsigned char)socksreq[2] << 8) | (unsigned char)socksreq[3]),
532          (unsigned char)socksreq[1]);
533    return CURLPX_IDENTD;
534  case 93:
535    failf(data,
536          "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)"
537          ", request rejected because the client program and identd "
538          "report different user-ids.",
539          socksreq[4], socksreq[5], socksreq[6], socksreq[7],
540          (((unsigned char)socksreq[2] << 8) | (unsigned char)socksreq[3]),
541          (unsigned char)socksreq[1]);
542    return CURLPX_IDENTD_DIFFER;
543  default:
544    failf(data,
545          "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)"
546          ", Unknown.",
547          socksreq[4], socksreq[5], socksreq[6], socksreq[7],
548          (((unsigned char)socksreq[2] << 8) | (unsigned char)socksreq[3]),
549          (unsigned char)socksreq[1]);
550    return CURLPX_UNKNOWN_FAIL;
551  }
552
553  return CURLPX_OK; /* Proxy was successful! */
554}
555
556/*
557 * This function logs in to a SOCKS5 proxy and sends the specifics to the final
558 * destination server.
559 */
560static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf,
561                               struct socks_state *sx,
562                               struct Curl_easy *data)
563{
564  /*
565    According to the RFC1928, section "6.  Replies". This is what a SOCK5
566    replies:
567
568        +----+-----+-------+------+----------+----------+
569        |VER | REP |  RSV  | ATYP | BND.ADDR | BND.PORT |
570        +----+-----+-------+------+----------+----------+
571        | 1  |  1  | X'00' |  1   | Variable |    2     |
572        +----+-----+-------+------+----------+----------+
573
574    Where:
575
576    o  VER    protocol version: X'05'
577    o  REP    Reply field:
578    o  X'00' succeeded
579  */
580  struct connectdata *conn = cf->conn;
581  unsigned char *socksreq = sx->buffer;
582  size_t idx;
583  CURLcode result;
584  CURLproxycode presult;
585  bool socks5_resolve_local =
586    (conn->socks_proxy.proxytype == CURLPROXY_SOCKS5) ? TRUE : FALSE;
587  const size_t hostname_len = strlen(sx->hostname);
588  size_t len = 0;
589  const unsigned char auth = data->set.socks5auth;
590  bool allow_gssapi = FALSE;
591  struct Curl_dns_entry *dns = NULL;
592
593  DEBUGASSERT(auth & (CURLAUTH_BASIC | CURLAUTH_GSSAPI));
594  switch(sx->state) {
595  case CONNECT_SOCKS_INIT:
596    if(conn->bits.httpproxy)
597      infof(data, "SOCKS5: connecting to HTTP proxy %s port %d",
598            sx->hostname, sx->remote_port);
599
600    /* RFC1928 chapter 5 specifies max 255 chars for domain name in packet */
601    if(!socks5_resolve_local && hostname_len > 255) {
602      failf(data, "SOCKS5: the destination hostname is too long to be "
603            "resolved remotely by the proxy.");
604      return CURLPX_LONG_HOSTNAME;
605    }
606
607    if(auth & ~(CURLAUTH_BASIC | CURLAUTH_GSSAPI))
608      infof(data,
609            "warning: unsupported value passed to CURLOPT_SOCKS5_AUTH: %u",
610            auth);
611    if(!(auth & CURLAUTH_BASIC))
612      /* disable username/password auth */
613      sx->proxy_user = NULL;
614#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
615    if(auth & CURLAUTH_GSSAPI)
616      allow_gssapi = TRUE;
617#endif
618
619    idx = 0;
620    socksreq[idx++] = 5;   /* version */
621    idx++;                 /* number of authentication methods */
622    socksreq[idx++] = 0;   /* no authentication */
623    if(allow_gssapi)
624      socksreq[idx++] = 1; /* GSS-API */
625    if(sx->proxy_user)
626      socksreq[idx++] = 2; /* username/password */
627    /* write the number of authentication methods */
628    socksreq[1] = (unsigned char) (idx - 2);
629
630    sx->outp = socksreq;
631    DEBUGASSERT(idx <= sizeof(sx->buffer));
632    sx->outstanding = idx;
633    presult = socks_state_send(cf, sx, data, CURLPX_SEND_CONNECT,
634                               "initial SOCKS5 request");
635    if(CURLPX_OK != presult)
636      return presult;
637    else if(sx->outstanding) {
638      /* remain in sending state */
639      return CURLPX_OK;
640    }
641    sxstate(sx, data, CONNECT_SOCKS_READ);
642    goto CONNECT_SOCKS_READ_INIT;
643  case CONNECT_SOCKS_SEND:
644    presult = socks_state_send(cf, sx, data, CURLPX_SEND_CONNECT,
645                               "initial SOCKS5 request");
646    if(CURLPX_OK != presult)
647      return presult;
648    else if(sx->outstanding) {
649      /* remain in sending state */
650      return CURLPX_OK;
651    }
652    FALLTHROUGH();
653  case CONNECT_SOCKS_READ_INIT:
654CONNECT_SOCKS_READ_INIT:
655    sx->outstanding = 2; /* expect two bytes */
656    sx->outp = socksreq; /* store it here */
657    FALLTHROUGH();
658  case CONNECT_SOCKS_READ:
659    presult = socks_state_recv(cf, sx, data, CURLPX_RECV_CONNECT,
660                               "initial SOCKS5 response");
661    if(CURLPX_OK != presult)
662      return presult;
663    else if(sx->outstanding) {
664      /* remain in reading state */
665      return CURLPX_OK;
666    }
667    else if(socksreq[0] != 5) {
668      failf(data, "Received invalid version in initial SOCKS5 response.");
669      return CURLPX_BAD_VERSION;
670    }
671    else if(socksreq[1] == 0) {
672      /* DONE! No authentication needed. Send request. */
673      sxstate(sx, data, CONNECT_REQ_INIT);
674      goto CONNECT_REQ_INIT;
675    }
676    else if(socksreq[1] == 2) {
677      /* regular name + password authentication */
678      sxstate(sx, data, CONNECT_AUTH_INIT);
679      goto CONNECT_AUTH_INIT;
680    }
681#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
682    else if(allow_gssapi && (socksreq[1] == 1)) {
683      sxstate(sx, data, CONNECT_GSSAPI_INIT);
684      result = Curl_SOCKS5_gssapi_negotiate(cf, data);
685      if(result) {
686        failf(data, "Unable to negotiate SOCKS5 GSS-API context.");
687        return CURLPX_GSSAPI;
688      }
689    }
690#endif
691    else {
692      /* error */
693      if(!allow_gssapi && (socksreq[1] == 1)) {
694        failf(data,
695              "SOCKS5 GSSAPI per-message authentication is not supported.");
696        return CURLPX_GSSAPI_PERMSG;
697      }
698      else if(socksreq[1] == 255) {
699        failf(data, "No authentication method was acceptable.");
700        return CURLPX_NO_AUTH;
701      }
702    }
703    failf(data,
704          "Undocumented SOCKS5 mode attempted to be used by server.");
705    return CURLPX_UNKNOWN_MODE;
706#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
707  case CONNECT_GSSAPI_INIT:
708    /* GSSAPI stuff done non-blocking */
709    break;
710#endif
711
712  default: /* do nothing! */
713    break;
714
715CONNECT_AUTH_INIT:
716  case CONNECT_AUTH_INIT: {
717    /* Needs user name and password */
718    size_t proxy_user_len, proxy_password_len;
719    if(sx->proxy_user && sx->proxy_password) {
720      proxy_user_len = strlen(sx->proxy_user);
721      proxy_password_len = strlen(sx->proxy_password);
722    }
723    else {
724      proxy_user_len = 0;
725      proxy_password_len = 0;
726    }
727
728    /*   username/password request looks like
729     * +----+------+----------+------+----------+
730     * |VER | ULEN |  UNAME   | PLEN |  PASSWD  |
731     * +----+------+----------+------+----------+
732     * | 1  |  1   | 1 to 255 |  1   | 1 to 255 |
733     * +----+------+----------+------+----------+
734     */
735    len = 0;
736    socksreq[len++] = 1;    /* username/pw subnegotiation version */
737    socksreq[len++] = (unsigned char) proxy_user_len;
738    if(sx->proxy_user && proxy_user_len) {
739      /* the length must fit in a single byte */
740      if(proxy_user_len > 255) {
741        failf(data, "Excessive user name length for proxy auth");
742        return CURLPX_LONG_USER;
743      }
744      memcpy(socksreq + len, sx->proxy_user, proxy_user_len);
745    }
746    len += proxy_user_len;
747    socksreq[len++] = (unsigned char) proxy_password_len;
748    if(sx->proxy_password && proxy_password_len) {
749      /* the length must fit in a single byte */
750      if(proxy_password_len > 255) {
751        failf(data, "Excessive password length for proxy auth");
752        return CURLPX_LONG_PASSWD;
753      }
754      memcpy(socksreq + len, sx->proxy_password, proxy_password_len);
755    }
756    len += proxy_password_len;
757    sxstate(sx, data, CONNECT_AUTH_SEND);
758    DEBUGASSERT(len <= sizeof(sx->buffer));
759    sx->outstanding = len;
760    sx->outp = socksreq;
761  }
762    FALLTHROUGH();
763  case CONNECT_AUTH_SEND:
764    presult = socks_state_send(cf, sx, data, CURLPX_SEND_AUTH,
765                               "SOCKS5 sub-negotiation request");
766    if(CURLPX_OK != presult)
767      return presult;
768    else if(sx->outstanding) {
769      /* remain in sending state */
770      return CURLPX_OK;
771    }
772    sx->outp = socksreq;
773    sx->outstanding = 2;
774    sxstate(sx, data, CONNECT_AUTH_READ);
775    FALLTHROUGH();
776  case CONNECT_AUTH_READ:
777    presult = socks_state_recv(cf, sx, data, CURLPX_RECV_AUTH,
778                               "SOCKS5 sub-negotiation response");
779    if(CURLPX_OK != presult)
780      return presult;
781    else if(sx->outstanding) {
782      /* remain in reading state */
783      return CURLPX_OK;
784    }
785    /* ignore the first (VER) byte */
786    else if(socksreq[1]) { /* status */
787      failf(data, "User was rejected by the SOCKS5 server (%d %d).",
788            socksreq[0], socksreq[1]);
789      return CURLPX_USER_REJECTED;
790    }
791
792    /* Everything is good so far, user was authenticated! */
793    sxstate(sx, data, CONNECT_REQ_INIT);
794    FALLTHROUGH();
795  case CONNECT_REQ_INIT:
796CONNECT_REQ_INIT:
797    if(socks5_resolve_local) {
798      enum resolve_t rc = Curl_resolv(data, sx->hostname, sx->remote_port,
799                                      TRUE, &dns);
800
801      if(rc == CURLRESOLV_ERROR)
802        return CURLPX_RESOLVE_HOST;
803
804      if(rc == CURLRESOLV_PENDING) {
805        sxstate(sx, data, CONNECT_RESOLVING);
806        return CURLPX_OK;
807      }
808      sxstate(sx, data, CONNECT_RESOLVED);
809      goto CONNECT_RESOLVED;
810    }
811    goto CONNECT_RESOLVE_REMOTE;
812
813  case CONNECT_RESOLVING:
814    /* check if we have the name resolved by now */
815    dns = Curl_fetch_addr(data, sx->hostname, sx->remote_port);
816
817    if(dns) {
818#ifdef CURLRES_ASYNCH
819      data->state.async.dns = dns;
820      data->state.async.done = TRUE;
821#endif
822      infof(data, "SOCKS5: hostname '%s' found", sx->hostname);
823    }
824
825    if(!dns) {
826      result = Curl_resolv_check(data, &dns);
827      if(!dns) {
828        if(result)
829          return CURLPX_RESOLVE_HOST;
830        return CURLPX_OK;
831      }
832    }
833    FALLTHROUGH();
834  case CONNECT_RESOLVED:
835CONNECT_RESOLVED:
836  {
837    char dest[MAX_IPADR_LEN];  /* printable address */
838    struct Curl_addrinfo *hp = NULL;
839    if(dns)
840      hp = dns->addr;
841#ifdef ENABLE_IPV6
842    if(data->set.ipver != CURL_IPRESOLVE_WHATEVER) {
843      int wanted_family = data->set.ipver == CURL_IPRESOLVE_V4 ?
844        AF_INET : AF_INET6;
845      /* scan for the first proper address */
846      while(hp && (hp->ai_family != wanted_family))
847        hp = hp->ai_next;
848    }
849#endif
850    if(!hp) {
851      failf(data, "Failed to resolve \"%s\" for SOCKS5 connect.",
852            sx->hostname);
853      return CURLPX_RESOLVE_HOST;
854    }
855
856    Curl_printable_address(hp, dest, sizeof(dest));
857
858    len = 0;
859    socksreq[len++] = 5; /* version (SOCKS5) */
860    socksreq[len++] = 1; /* connect */
861    socksreq[len++] = 0; /* must be zero */
862    if(hp->ai_family == AF_INET) {
863      int i;
864      struct sockaddr_in *saddr_in;
865      socksreq[len++] = 1; /* ATYP: IPv4 = 1 */
866
867      saddr_in = (struct sockaddr_in *)(void *)hp->ai_addr;
868      for(i = 0; i < 4; i++) {
869        socksreq[len++] = ((unsigned char *)&saddr_in->sin_addr.s_addr)[i];
870      }
871
872      infof(data, "SOCKS5 connect to %s:%d (locally resolved)", dest,
873            sx->remote_port);
874    }
875#ifdef ENABLE_IPV6
876    else if(hp->ai_family == AF_INET6) {
877      int i;
878      struct sockaddr_in6 *saddr_in6;
879      socksreq[len++] = 4; /* ATYP: IPv6 = 4 */
880
881      saddr_in6 = (struct sockaddr_in6 *)(void *)hp->ai_addr;
882      for(i = 0; i < 16; i++) {
883        socksreq[len++] =
884          ((unsigned char *)&saddr_in6->sin6_addr.s6_addr)[i];
885      }
886
887      infof(data, "SOCKS5 connect to [%s]:%d (locally resolved)", dest,
888            sx->remote_port);
889    }
890#endif
891    else {
892      hp = NULL; /* fail! */
893      failf(data, "SOCKS5 connection to %s not supported", dest);
894    }
895
896    Curl_resolv_unlock(data, dns); /* not used anymore from now on */
897    goto CONNECT_REQ_SEND;
898  }
899CONNECT_RESOLVE_REMOTE:
900  case CONNECT_RESOLVE_REMOTE:
901    /* Authentication is complete, now specify destination to the proxy */
902    len = 0;
903    socksreq[len++] = 5; /* version (SOCKS5) */
904    socksreq[len++] = 1; /* connect */
905    socksreq[len++] = 0; /* must be zero */
906
907    if(!socks5_resolve_local) {
908      /* ATYP: domain name = 3,
909         IPv6 == 4,
910         IPv4 == 1 */
911      unsigned char ip4[4];
912#ifdef ENABLE_IPV6
913      if(conn->bits.ipv6_ip) {
914        char ip6[16];
915        if(1 != Curl_inet_pton(AF_INET6, sx->hostname, ip6))
916          return CURLPX_BAD_ADDRESS_TYPE;
917        socksreq[len++] = 4;
918        memcpy(&socksreq[len], ip6, sizeof(ip6));
919        len += sizeof(ip6);
920      }
921      else
922#endif
923      if(1 == Curl_inet_pton(AF_INET, sx->hostname, ip4)) {
924        socksreq[len++] = 1;
925        memcpy(&socksreq[len], ip4, sizeof(ip4));
926        len += sizeof(ip4);
927      }
928      else {
929        socksreq[len++] = 3;
930        socksreq[len++] = (unsigned char) hostname_len; /* one byte length */
931        memcpy(&socksreq[len], sx->hostname, hostname_len); /* w/o NULL */
932        len += hostname_len;
933      }
934      infof(data, "SOCKS5 connect to %s:%d (remotely resolved)",
935            sx->hostname, sx->remote_port);
936    }
937    FALLTHROUGH();
938
939  case CONNECT_REQ_SEND:
940CONNECT_REQ_SEND:
941    /* PORT MSB */
942    socksreq[len++] = (unsigned char)((sx->remote_port >> 8) & 0xff);
943    /* PORT LSB */
944    socksreq[len++] = (unsigned char)(sx->remote_port & 0xff);
945
946#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
947    if(conn->socks5_gssapi_enctype) {
948      failf(data, "SOCKS5 GSS-API protection not yet implemented.");
949      return CURLPX_GSSAPI_PROTECTION;
950    }
951#endif
952    sx->outp = socksreq;
953    DEBUGASSERT(len <= sizeof(sx->buffer));
954    sx->outstanding = len;
955    sxstate(sx, data, CONNECT_REQ_SENDING);
956    FALLTHROUGH();
957  case CONNECT_REQ_SENDING:
958    presult = socks_state_send(cf, sx, data, CURLPX_SEND_REQUEST,
959                               "SOCKS5 connect request");
960    if(CURLPX_OK != presult)
961      return presult;
962    else if(sx->outstanding) {
963      /* remain in send state */
964      return CURLPX_OK;
965    }
966#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
967    if(conn->socks5_gssapi_enctype) {
968      failf(data, "SOCKS5 GSS-API protection not yet implemented.");
969      return CURLPX_GSSAPI_PROTECTION;
970    }
971#endif
972    sx->outstanding = 10; /* minimum packet size is 10 */
973    sx->outp = socksreq;
974    sxstate(sx, data, CONNECT_REQ_READ);
975    FALLTHROUGH();
976  case CONNECT_REQ_READ:
977    presult = socks_state_recv(cf, sx, data, CURLPX_RECV_REQACK,
978                               "SOCKS5 connect request ack");
979    if(CURLPX_OK != presult)
980      return presult;
981    else if(sx->outstanding) {
982      /* remain in reading state */
983      return CURLPX_OK;
984    }
985    else if(socksreq[0] != 5) { /* version */
986      failf(data,
987            "SOCKS5 reply has wrong version, version should be 5.");
988      return CURLPX_BAD_VERSION;
989    }
990    else if(socksreq[1]) { /* Anything besides 0 is an error */
991      CURLproxycode rc = CURLPX_REPLY_UNASSIGNED;
992      int code = socksreq[1];
993      failf(data, "Can't complete SOCKS5 connection to %s. (%d)",
994            sx->hostname, (unsigned char)socksreq[1]);
995      if(code < 9) {
996        /* RFC 1928 section 6 lists: */
997        static const CURLproxycode lookup[] = {
998          CURLPX_OK,
999          CURLPX_REPLY_GENERAL_SERVER_FAILURE,
1000          CURLPX_REPLY_NOT_ALLOWED,
1001          CURLPX_REPLY_NETWORK_UNREACHABLE,
1002          CURLPX_REPLY_HOST_UNREACHABLE,
1003          CURLPX_REPLY_CONNECTION_REFUSED,
1004          CURLPX_REPLY_TTL_EXPIRED,
1005          CURLPX_REPLY_COMMAND_NOT_SUPPORTED,
1006          CURLPX_REPLY_ADDRESS_TYPE_NOT_SUPPORTED,
1007        };
1008        rc = lookup[code];
1009      }
1010      return rc;
1011    }
1012
1013    /* Fix: in general, returned BND.ADDR is variable length parameter by RFC
1014       1928, so the reply packet should be read until the end to avoid errors
1015       at subsequent protocol level.
1016
1017       +----+-----+-------+------+----------+----------+
1018       |VER | REP |  RSV  | ATYP | BND.ADDR | BND.PORT |
1019       +----+-----+-------+------+----------+----------+
1020       | 1  |  1  | X'00' |  1   | Variable |    2     |
1021       +----+-----+-------+------+----------+----------+
1022
1023       ATYP:
1024       o  IP v4 address: X'01', BND.ADDR = 4 byte
1025       o  domain name:  X'03', BND.ADDR = [ 1 byte length, string ]
1026       o  IP v6 address: X'04', BND.ADDR = 16 byte
1027    */
1028
1029    /* Calculate real packet size */
1030    if(socksreq[3] == 3) {
1031      /* domain name */
1032      int addrlen = (int) socksreq[4];
1033      len = 5 + addrlen + 2;
1034    }
1035    else if(socksreq[3] == 4) {
1036      /* IPv6 */
1037      len = 4 + 16 + 2;
1038    }
1039    else if(socksreq[3] == 1) {
1040      len = 4 + 4 + 2;
1041    }
1042    else {
1043      failf(data, "SOCKS5 reply has wrong address type.");
1044      return CURLPX_BAD_ADDRESS_TYPE;
1045    }
1046
1047    /* At this point we already read first 10 bytes */
1048#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
1049    if(!conn->socks5_gssapi_enctype) {
1050      /* decrypt_gssapi_blockread already read the whole packet */
1051#endif
1052      if(len > 10) {
1053        DEBUGASSERT(len <= sizeof(sx->buffer));
1054        sx->outstanding = len - 10; /* get the rest */
1055        sx->outp = &socksreq[10];
1056        sxstate(sx, data, CONNECT_REQ_READ_MORE);
1057      }
1058      else {
1059        sxstate(sx, data, CONNECT_DONE);
1060        break;
1061      }
1062#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
1063    }
1064#endif
1065    FALLTHROUGH();
1066  case CONNECT_REQ_READ_MORE:
1067    presult = socks_state_recv(cf, sx, data, CURLPX_RECV_ADDRESS,
1068                               "SOCKS5 connect request address");
1069    if(CURLPX_OK != presult)
1070      return presult;
1071    else if(sx->outstanding) {
1072      /* remain in reading state */
1073      return CURLPX_OK;
1074    }
1075    sxstate(sx, data, CONNECT_DONE);
1076  }
1077  infof(data, "SOCKS5 request granted.");
1078
1079  return CURLPX_OK; /* Proxy was successful! */
1080}
1081
1082static CURLcode connect_SOCKS(struct Curl_cfilter *cf,
1083                              struct socks_state *sxstate,
1084                              struct Curl_easy *data)
1085{
1086  CURLcode result = CURLE_OK;
1087  CURLproxycode pxresult = CURLPX_OK;
1088  struct connectdata *conn = cf->conn;
1089
1090  switch(conn->socks_proxy.proxytype) {
1091  case CURLPROXY_SOCKS5:
1092  case CURLPROXY_SOCKS5_HOSTNAME:
1093    pxresult = do_SOCKS5(cf, sxstate, data);
1094    break;
1095
1096  case CURLPROXY_SOCKS4:
1097  case CURLPROXY_SOCKS4A:
1098    pxresult = do_SOCKS4(cf, sxstate, data);
1099    break;
1100
1101  default:
1102    failf(data, "unknown proxytype option given");
1103    result = CURLE_COULDNT_CONNECT;
1104  } /* switch proxytype */
1105  if(pxresult) {
1106    result = CURLE_PROXY;
1107    data->info.pxcode = pxresult;
1108  }
1109
1110  return result;
1111}
1112
1113static void socks_proxy_cf_free(struct Curl_cfilter *cf)
1114{
1115  struct socks_state *sxstate = cf->ctx;
1116  if(sxstate) {
1117    free(sxstate);
1118    cf->ctx = NULL;
1119  }
1120}
1121
1122/* After a TCP connection to the proxy has been verified, this function does
1123   the next magic steps. If 'done' isn't set TRUE, it is not done yet and
1124   must be called again.
1125
1126   Note: this function's sub-functions call failf()
1127
1128*/
1129static CURLcode socks_proxy_cf_connect(struct Curl_cfilter *cf,
1130                                       struct Curl_easy *data,
1131                                       bool blocking, bool *done)
1132{
1133  CURLcode result;
1134  struct connectdata *conn = cf->conn;
1135  int sockindex = cf->sockindex;
1136  struct socks_state *sx = cf->ctx;
1137
1138  if(cf->connected) {
1139    *done = TRUE;
1140    return CURLE_OK;
1141  }
1142
1143  result = cf->next->cft->do_connect(cf->next, data, blocking, done);
1144  if(result || !*done)
1145    return result;
1146
1147  if(!sx) {
1148    sx = calloc(1, sizeof(*sx));
1149    if(!sx)
1150      return CURLE_OUT_OF_MEMORY;
1151    cf->ctx = sx;
1152  }
1153
1154  if(sx->state == CONNECT_INIT) {
1155    /* for the secondary socket (FTP), use the "connect to host"
1156     * but ignore the "connect to port" (use the secondary port)
1157     */
1158    sxstate(sx, data, CONNECT_SOCKS_INIT);
1159    sx->hostname =
1160      conn->bits.httpproxy ?
1161      conn->http_proxy.host.name :
1162      conn->bits.conn_to_host ?
1163      conn->conn_to_host.name :
1164      sockindex == SECONDARYSOCKET ?
1165      conn->secondaryhostname : conn->host.name;
1166    sx->remote_port =
1167      conn->bits.httpproxy ? (int)conn->http_proxy.port :
1168      sockindex == SECONDARYSOCKET ? conn->secondary_port :
1169      conn->bits.conn_to_port ? conn->conn_to_port :
1170      conn->remote_port;
1171    sx->proxy_user = conn->socks_proxy.user;
1172    sx->proxy_password = conn->socks_proxy.passwd;
1173  }
1174
1175  result = connect_SOCKS(cf, sx, data);
1176  if(!result && sx->state == CONNECT_DONE) {
1177    cf->connected = TRUE;
1178    Curl_verboseconnect(data, conn);
1179    socks_proxy_cf_free(cf);
1180  }
1181
1182  *done = cf->connected;
1183  return result;
1184}
1185
1186static void socks_cf_adjust_pollset(struct Curl_cfilter *cf,
1187                                     struct Curl_easy *data,
1188                                     struct easy_pollset *ps)
1189{
1190  struct socks_state *sx = cf->ctx;
1191
1192  if(!cf->connected && sx) {
1193    /* If we are not connected, the filter below is and has nothing
1194     * to wait on, we determine what to wait for. */
1195    curl_socket_t sock = Curl_conn_cf_get_socket(cf, data);
1196    switch(sx->state) {
1197    case CONNECT_RESOLVING:
1198    case CONNECT_SOCKS_READ:
1199    case CONNECT_AUTH_READ:
1200    case CONNECT_REQ_READ:
1201    case CONNECT_REQ_READ_MORE:
1202      Curl_pollset_set_in_only(data, ps, sock);
1203      break;
1204    default:
1205      Curl_pollset_set_out_only(data, ps, sock);
1206      break;
1207    }
1208  }
1209}
1210
1211static void socks_proxy_cf_close(struct Curl_cfilter *cf,
1212                                 struct Curl_easy *data)
1213{
1214
1215  DEBUGASSERT(cf->next);
1216  cf->connected = FALSE;
1217  socks_proxy_cf_free(cf);
1218  cf->next->cft->do_close(cf->next, data);
1219}
1220
1221static void socks_proxy_cf_destroy(struct Curl_cfilter *cf,
1222                                   struct Curl_easy *data)
1223{
1224  (void)data;
1225  socks_proxy_cf_free(cf);
1226}
1227
1228static void socks_cf_get_host(struct Curl_cfilter *cf,
1229                              struct Curl_easy *data,
1230                              const char **phost,
1231                              const char **pdisplay_host,
1232                              int *pport)
1233{
1234  (void)data;
1235  if(!cf->connected) {
1236    *phost = cf->conn->socks_proxy.host.name;
1237    *pdisplay_host = cf->conn->http_proxy.host.dispname;
1238    *pport = (int)cf->conn->socks_proxy.port;
1239  }
1240  else {
1241    cf->next->cft->get_host(cf->next, data, phost, pdisplay_host, pport);
1242  }
1243}
1244
1245struct Curl_cftype Curl_cft_socks_proxy = {
1246  "SOCKS-PROXYY",
1247  CF_TYPE_IP_CONNECT,
1248  0,
1249  socks_proxy_cf_destroy,
1250  socks_proxy_cf_connect,
1251  socks_proxy_cf_close,
1252  socks_cf_get_host,
1253  socks_cf_adjust_pollset,
1254  Curl_cf_def_data_pending,
1255  Curl_cf_def_send,
1256  Curl_cf_def_recv,
1257  Curl_cf_def_cntrl,
1258  Curl_cf_def_conn_is_alive,
1259  Curl_cf_def_conn_keep_alive,
1260  Curl_cf_def_query,
1261};
1262
1263CURLcode Curl_cf_socks_proxy_insert_after(struct Curl_cfilter *cf_at,
1264                                          struct Curl_easy *data)
1265{
1266  struct Curl_cfilter *cf;
1267  CURLcode result;
1268
1269  (void)data;
1270  result = Curl_cf_create(&cf, &Curl_cft_socks_proxy, NULL);
1271  if(!result)
1272    Curl_conn_cf_insert_after(cf_at, cf);
1273  return result;
1274}
1275
1276#endif /* CURL_DISABLE_PROXY */
1277