static CURLproxycode do_SOCKS5()

in libs/curl/lib/socks.c [560:1080]


static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf,
                               struct socks_state *sx,
                               struct Curl_easy *data)
{
  /*
    According to the RFC1928, section "6. Replies". This is what a SOCK5
    replies:

        +----+-----+-------+------+----------+----------+
        |VER | REP |  RSV  | ATYP | BND.ADDR | BND.PORT |
        +----+-----+-------+------+----------+----------+
        | 1  |  1  | X'00' |  1   | Variable |    2     |
        +----+-----+-------+------+----------+----------+

    Where:

    o  VER    protocol version: X'05'
    o  REP    Reply field:
    o  X'00' succeeded
  */
  struct connectdata *conn = cf->conn;
  unsigned char *socksreq = sx->buffer;
  size_t idx;
  CURLcode result;
  CURLproxycode presult;
  bool socks5_resolve_local =
    (conn->socks_proxy.proxytype == CURLPROXY_SOCKS5) ? TRUE : FALSE;
  const size_t hostname_len = strlen(sx->hostname);
  size_t len = 0;
  const unsigned char auth = data->set.socks5auth;
  bool allow_gssapi = FALSE;
  struct Curl_dns_entry *dns = NULL;

  DEBUGASSERT(auth & (CURLAUTH_BASIC | CURLAUTH_GSSAPI));
  switch(sx->state) {
  case CONNECT_SOCKS_INIT:
    if(conn->bits.httpproxy)
      infof(data, "SOCKS5: connecting to HTTP proxy %s port %d",
            sx->hostname, sx->remote_port);

    /* RFC1928 chapter 5 specifies max 255 chars for domain name in packet */
    if(!socks5_resolve_local && hostname_len > 255) {
      failf(data, "SOCKS5: the destination hostname is too long to be "
            "resolved remotely by the proxy.");
      return CURLPX_LONG_HOSTNAME;
    }

    if(auth & ~(CURLAUTH_BASIC | CURLAUTH_GSSAPI))
      infof(data,
            "warning: unsupported value passed to CURLOPT_SOCKS5_AUTH: %u",
            auth);
    if(!(auth & CURLAUTH_BASIC))
      /* disable username/password auth */
      sx->proxy_user = NULL;
#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
    if(auth & CURLAUTH_GSSAPI)
      allow_gssapi = TRUE;
#endif

    idx = 0;
    socksreq[idx++] = 5;   /* version */
    idx++;                 /* number of authentication methods */
    socksreq[idx++] = 0;   /* no authentication */
    if(allow_gssapi)
      socksreq[idx++] = 1; /* GSS-API */
    if(sx->proxy_user)
      socksreq[idx++] = 2; /* username/password */
    /* write the number of authentication methods */
    socksreq[1] = (unsigned char) (idx - 2);

    sx->outp = socksreq;
    DEBUGASSERT(idx <= sizeof(sx->buffer));
    sx->outstanding = idx;
    presult = socks_state_send(cf, sx, data, CURLPX_SEND_CONNECT,
                               "initial SOCKS5 request");
    if(CURLPX_OK != presult)
      return presult;
    else if(sx->outstanding) {
      /* remain in sending state */
      return CURLPX_OK;
    }
    sxstate(sx, data, CONNECT_SOCKS_READ);
    goto CONNECT_SOCKS_READ_INIT;
  case CONNECT_SOCKS_SEND:
    presult = socks_state_send(cf, sx, data, CURLPX_SEND_CONNECT,
                               "initial SOCKS5 request");
    if(CURLPX_OK != presult)
      return presult;
    else if(sx->outstanding) {
      /* remain in sending state */
      return CURLPX_OK;
    }
    FALLTHROUGH();
  case CONNECT_SOCKS_READ_INIT:
CONNECT_SOCKS_READ_INIT:
    sx->outstanding = 2; /* expect two bytes */
    sx->outp = socksreq; /* store it here */
    FALLTHROUGH();
  case CONNECT_SOCKS_READ:
    presult = socks_state_recv(cf, sx, data, CURLPX_RECV_CONNECT,
                               "initial SOCKS5 response");
    if(CURLPX_OK != presult)
      return presult;
    else if(sx->outstanding) {
      /* remain in reading state */
      return CURLPX_OK;
    }
    else if(socksreq[0] != 5) {
      failf(data, "Received invalid version in initial SOCKS5 response.");
      return CURLPX_BAD_VERSION;
    }
    else if(socksreq[1] == 0) {
      /* DONE! No authentication needed. Send request. */
      sxstate(sx, data, CONNECT_REQ_INIT);
      goto CONNECT_REQ_INIT;
    }
    else if(socksreq[1] == 2) {
      /* regular name + password authentication */
      sxstate(sx, data, CONNECT_AUTH_INIT);
      goto CONNECT_AUTH_INIT;
    }
#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
    else if(allow_gssapi && (socksreq[1] == 1)) {
      sxstate(sx, data, CONNECT_GSSAPI_INIT);
      result = Curl_SOCKS5_gssapi_negotiate(cf, data);
      if(result) {
        failf(data, "Unable to negotiate SOCKS5 GSS-API context.");
        return CURLPX_GSSAPI;
      }
    }
#endif
    else {
      /* error */
      if(!allow_gssapi && (socksreq[1] == 1)) {
        failf(data,
              "SOCKS5 GSSAPI per-message authentication is not supported.");
        return CURLPX_GSSAPI_PERMSG;
      }
      else if(socksreq[1] == 255) {
        failf(data, "No authentication method was acceptable.");
        return CURLPX_NO_AUTH;
      }
    }
    failf(data,
          "Undocumented SOCKS5 mode attempted to be used by server.");
    return CURLPX_UNKNOWN_MODE;
#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
  case CONNECT_GSSAPI_INIT:
    /* GSSAPI stuff done non-blocking */
    break;
#endif

  default: /* do nothing! */
    break;

CONNECT_AUTH_INIT:
  case CONNECT_AUTH_INIT: {
    /* Needs username and password */
    size_t proxy_user_len, proxy_password_len;
    if(sx->proxy_user && sx->proxy_password) {
      proxy_user_len = strlen(sx->proxy_user);
      proxy_password_len = strlen(sx->proxy_password);
    }
    else {
      proxy_user_len = 0;
      proxy_password_len = 0;
    }

    /*   username/password request looks like
     * +----+------+----------+------+----------+
     * |VER | ULEN |  UNAME   | PLEN |  PASSWD  |
     * +----+------+----------+------+----------+
     * | 1  |  1   | 1 to 255 |  1   | 1 to 255 |
     * +----+------+----------+------+----------+
     */
    len = 0;
    socksreq[len++] = 1;    /* username/pw subnegotiation version */
    socksreq[len++] = (unsigned char) proxy_user_len;
    if(sx->proxy_user && proxy_user_len) {
      /* the length must fit in a single byte */
      if(proxy_user_len > 255) {
        failf(data, "Excessive username length for proxy auth");
        return CURLPX_LONG_USER;
      }
      memcpy(socksreq + len, sx->proxy_user, proxy_user_len);
    }
    len += proxy_user_len;
    socksreq[len++] = (unsigned char) proxy_password_len;
    if(sx->proxy_password && proxy_password_len) {
      /* the length must fit in a single byte */
      if(proxy_password_len > 255) {
        failf(data, "Excessive password length for proxy auth");
        return CURLPX_LONG_PASSWD;
      }
      memcpy(socksreq + len, sx->proxy_password, proxy_password_len);
    }
    len += proxy_password_len;
    sxstate(sx, data, CONNECT_AUTH_SEND);
    DEBUGASSERT(len <= sizeof(sx->buffer));
    sx->outstanding = len;
    sx->outp = socksreq;
  }
    FALLTHROUGH();
  case CONNECT_AUTH_SEND:
    presult = socks_state_send(cf, sx, data, CURLPX_SEND_AUTH,
                               "SOCKS5 sub-negotiation request");
    if(CURLPX_OK != presult)
      return presult;
    else if(sx->outstanding) {
      /* remain in sending state */
      return CURLPX_OK;
    }
    sx->outp = socksreq;
    sx->outstanding = 2;
    sxstate(sx, data, CONNECT_AUTH_READ);
    FALLTHROUGH();
  case CONNECT_AUTH_READ:
    presult = socks_state_recv(cf, sx, data, CURLPX_RECV_AUTH,
                               "SOCKS5 sub-negotiation response");
    if(CURLPX_OK != presult)
      return presult;
    else if(sx->outstanding) {
      /* remain in reading state */
      return CURLPX_OK;
    }
    /* ignore the first (VER) byte */
    else if(socksreq[1]) { /* status */
      failf(data, "User was rejected by the SOCKS5 server (%d %d).",
            socksreq[0], socksreq[1]);
      return CURLPX_USER_REJECTED;
    }

    /* Everything is good so far, user was authenticated! */
    sxstate(sx, data, CONNECT_REQ_INIT);
    FALLTHROUGH();
  case CONNECT_REQ_INIT:
CONNECT_REQ_INIT:
    if(socks5_resolve_local) {
      enum resolve_t rc = Curl_resolv(data, sx->hostname, sx->remote_port,
                                      TRUE, &dns);

      if(rc == CURLRESOLV_ERROR)
        return CURLPX_RESOLVE_HOST;

      if(rc == CURLRESOLV_PENDING) {
        sxstate(sx, data, CONNECT_RESOLVING);
        return CURLPX_OK;
      }
      sxstate(sx, data, CONNECT_RESOLVED);
      goto CONNECT_RESOLVED;
    }
    goto CONNECT_RESOLVE_REMOTE;

  case CONNECT_RESOLVING:
    /* check if we have the name resolved by now */
    dns = Curl_fetch_addr(data, sx->hostname, sx->remote_port);

    if(dns) {
#ifdef CURLRES_ASYNCH
      data->state.async.dns = dns;
      data->state.async.done = TRUE;
#endif
      infof(data, "SOCKS5: hostname '%s' found", sx->hostname);
    }

    if(!dns) {
      result = Curl_resolv_check(data, &dns);
      if(!dns) {
        if(result)
          return CURLPX_RESOLVE_HOST;
        return CURLPX_OK;
      }
    }
    FALLTHROUGH();
  case CONNECT_RESOLVED:
CONNECT_RESOLVED:
  {
    char dest[MAX_IPADR_LEN];  /* printable address */
    struct Curl_addrinfo *hp = NULL;
    if(dns)
      hp = dns->addr;
#ifdef USE_IPV6
    if(data->set.ipver != CURL_IPRESOLVE_WHATEVER) {
      int wanted_family = data->set.ipver == CURL_IPRESOLVE_V4 ?
        AF_INET : AF_INET6;
      /* scan for the first proper address */
      while(hp && (hp->ai_family != wanted_family))
        hp = hp->ai_next;
    }
#endif
    if(!hp) {
      failf(data, "Failed to resolve \"%s\" for SOCKS5 connect.",
            sx->hostname);
      return CURLPX_RESOLVE_HOST;
    }

    Curl_printable_address(hp, dest, sizeof(dest));

    len = 0;
    socksreq[len++] = 5; /* version (SOCKS5) */
    socksreq[len++] = 1; /* connect */
    socksreq[len++] = 0; /* must be zero */
    if(hp->ai_family == AF_INET) {
      int i;
      struct sockaddr_in *saddr_in;
      socksreq[len++] = 1; /* ATYP: IPv4 = 1 */

      saddr_in = (struct sockaddr_in *)(void *)hp->ai_addr;
      for(i = 0; i < 4; i++) {
        socksreq[len++] = ((unsigned char *)&saddr_in->sin_addr.s_addr)[i];
      }

      infof(data, "SOCKS5 connect to %s:%d (locally resolved)", dest,
            sx->remote_port);
    }
#ifdef USE_IPV6
    else if(hp->ai_family == AF_INET6) {
      int i;
      struct sockaddr_in6 *saddr_in6;
      socksreq[len++] = 4; /* ATYP: IPv6 = 4 */

      saddr_in6 = (struct sockaddr_in6 *)(void *)hp->ai_addr;
      for(i = 0; i < 16; i++) {
        socksreq[len++] =
          ((unsigned char *)&saddr_in6->sin6_addr.s6_addr)[i];
      }

      infof(data, "SOCKS5 connect to [%s]:%d (locally resolved)", dest,
            sx->remote_port);
    }
#endif
    else {
      hp = NULL; /* fail! */
      failf(data, "SOCKS5 connection to %s not supported", dest);
    }

    Curl_resolv_unlock(data, dns); /* not used anymore from now on */
    goto CONNECT_REQ_SEND;
  }
CONNECT_RESOLVE_REMOTE:
  case CONNECT_RESOLVE_REMOTE:
    /* Authentication is complete, now specify destination to the proxy */
    len = 0;
    socksreq[len++] = 5; /* version (SOCKS5) */
    socksreq[len++] = 1; /* connect */
    socksreq[len++] = 0; /* must be zero */

    if(!socks5_resolve_local) {
      /* ATYP: domain name = 3,
         IPv6 == 4,
         IPv4 == 1 */
      unsigned char ip4[4];
#ifdef USE_IPV6
      if(conn->bits.ipv6_ip) {
        char ip6[16];
        if(1 != Curl_inet_pton(AF_INET6, sx->hostname, ip6))
          return CURLPX_BAD_ADDRESS_TYPE;
        socksreq[len++] = 4;
        memcpy(&socksreq[len], ip6, sizeof(ip6));
        len += sizeof(ip6);
      }
      else
#endif
      if(1 == Curl_inet_pton(AF_INET, sx->hostname, ip4)) {
        socksreq[len++] = 1;
        memcpy(&socksreq[len], ip4, sizeof(ip4));
        len += sizeof(ip4);
      }
      else {
        socksreq[len++] = 3;
        socksreq[len++] = (unsigned char) hostname_len; /* one byte length */
        memcpy(&socksreq[len], sx->hostname, hostname_len); /* w/o NULL */
        len += hostname_len;
      }
      infof(data, "SOCKS5 connect to %s:%d (remotely resolved)",
            sx->hostname, sx->remote_port);
    }
    FALLTHROUGH();

  case CONNECT_REQ_SEND:
CONNECT_REQ_SEND:
    /* PORT MSB */
    socksreq[len++] = (unsigned char)((sx->remote_port >> 8) & 0xff);
    /* PORT LSB */
    socksreq[len++] = (unsigned char)(sx->remote_port & 0xff);

#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
    if(conn->socks5_gssapi_enctype) {
      failf(data, "SOCKS5 GSS-API protection not yet implemented.");
      return CURLPX_GSSAPI_PROTECTION;
    }
#endif
    sx->outp = socksreq;
    DEBUGASSERT(len <= sizeof(sx->buffer));
    sx->outstanding = len;
    sxstate(sx, data, CONNECT_REQ_SENDING);
    FALLTHROUGH();
  case CONNECT_REQ_SENDING:
    presult = socks_state_send(cf, sx, data, CURLPX_SEND_REQUEST,
                               "SOCKS5 connect request");
    if(CURLPX_OK != presult)
      return presult;
    else if(sx->outstanding) {
      /* remain in send state */
      return CURLPX_OK;
    }
#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
    if(conn->socks5_gssapi_enctype) {
      failf(data, "SOCKS5 GSS-API protection not yet implemented.");
      return CURLPX_GSSAPI_PROTECTION;
    }
#endif
    sx->outstanding = 10; /* minimum packet size is 10 */
    sx->outp = socksreq;
    sxstate(sx, data, CONNECT_REQ_READ);
    FALLTHROUGH();
  case CONNECT_REQ_READ:
    presult = socks_state_recv(cf, sx, data, CURLPX_RECV_REQACK,
                               "SOCKS5 connect request ack");
    if(CURLPX_OK != presult)
      return presult;
    else if(sx->outstanding) {
      /* remain in reading state */
      return CURLPX_OK;
    }
    else if(socksreq[0] != 5) { /* version */
      failf(data,
            "SOCKS5 reply has wrong version, version should be 5.");
      return CURLPX_BAD_VERSION;
    }
    else if(socksreq[1]) { /* Anything besides 0 is an error */
      CURLproxycode rc = CURLPX_REPLY_UNASSIGNED;
      int code = socksreq[1];
      failf(data, "cannot complete SOCKS5 connection to %s. (%d)",
            sx->hostname, (unsigned char)socksreq[1]);
      if(code < 9) {
        /* RFC 1928 section 6 lists: */
        static const CURLproxycode lookup[] = {
          CURLPX_OK,
          CURLPX_REPLY_GENERAL_SERVER_FAILURE,
          CURLPX_REPLY_NOT_ALLOWED,
          CURLPX_REPLY_NETWORK_UNREACHABLE,
          CURLPX_REPLY_HOST_UNREACHABLE,
          CURLPX_REPLY_CONNECTION_REFUSED,
          CURLPX_REPLY_TTL_EXPIRED,
          CURLPX_REPLY_COMMAND_NOT_SUPPORTED,
          CURLPX_REPLY_ADDRESS_TYPE_NOT_SUPPORTED,
        };
        rc = lookup[code];
      }
      return rc;
    }

    /* Fix: in general, returned BND.ADDR is variable length parameter by RFC
       1928, so the reply packet should be read until the end to avoid errors
       at subsequent protocol level.

       +----+-----+-------+------+----------+----------+
       |VER | REP |  RSV  | ATYP | BND.ADDR | BND.PORT |
       +----+-----+-------+------+----------+----------+
       | 1  |  1  | X'00' |  1   | Variable |    2     |
       +----+-----+-------+------+----------+----------+

       ATYP:
       o  IP v4 address: X'01', BND.ADDR = 4 byte
       o  domain name:  X'03', BND.ADDR = [ 1 byte length, string ]
       o  IP v6 address: X'04', BND.ADDR = 16 byte
    */

    /* Calculate real packet size */
    if(socksreq[3] == 3) {
      /* domain name */
      int addrlen = (int) socksreq[4];
      len = 5 + addrlen + 2;
    }
    else if(socksreq[3] == 4) {
      /* IPv6 */
      len = 4 + 16 + 2;
    }
    else if(socksreq[3] == 1) {
      len = 4 + 4 + 2;
    }
    else {
      failf(data, "SOCKS5 reply has wrong address type.");
      return CURLPX_BAD_ADDRESS_TYPE;
    }

    /* At this point we already read first 10 bytes */
#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
    if(!conn->socks5_gssapi_enctype) {
      /* decrypt_gssapi_blockread already read the whole packet */
#endif
      if(len > 10) {
        DEBUGASSERT(len <= sizeof(sx->buffer));
        sx->outstanding = len - 10; /* get the rest */
        sx->outp = &socksreq[10];
        sxstate(sx, data, CONNECT_REQ_READ_MORE);
      }
      else {
        sxstate(sx, data, CONNECT_DONE);
        break;
      }
#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
    }
#endif
    FALLTHROUGH();
  case CONNECT_REQ_READ_MORE:
    presult = socks_state_recv(cf, sx, data, CURLPX_RECV_ADDRESS,
                               "SOCKS5 connect request address");
    if(CURLPX_OK != presult)
      return presult;
    else if(sx->outstanding) {
      /* remain in reading state */
      return CURLPX_OK;
    }
    sxstate(sx, data, CONNECT_DONE);
  }
  infof(data, "SOCKS5 request granted.");

  return CURLPX_OK; /* Proxy was successful! */
}