diff options
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/connect.c | 81 | ||||
| -rw-r--r-- | lib/ftp.c | 9 | ||||
| -rw-r--r-- | lib/hostip.c | 24 | ||||
| -rw-r--r-- | lib/hostip.h | 27 | ||||
| -rw-r--r-- | lib/multi.c | 4 | ||||
| -rw-r--r-- | lib/sendf.c | 11 | ||||
| -rw-r--r-- | lib/socks.c | 1132 | ||||
| -rw-r--r-- | lib/socks.h | 15 | ||||
| -rw-r--r-- | lib/socks_gssapi.c | 6 | ||||
| -rw-r--r-- | lib/socks_sspi.c | 5 | ||||
| -rw-r--r-- | lib/urldata.h | 38 | 
11 files changed, 817 insertions, 535 deletions
| diff --git a/lib/connect.c b/lib/connect.c index 9baadb565..0cfd6886f 100644 --- a/lib/connect.c +++ b/lib/connect.c @@ -745,59 +745,83 @@ void Curl_updateconninfo(struct connectdata *conn, curl_socket_t sockfd)    Curl_persistconninfo(conn);  } -/* after a TCP connection to the proxy has been verified, this function does -   the next magic step. +/* After a TCP connection to the proxy has been verified, this function does +   the next magic steps. If 'done' isn't set TRUE, it is not done yet and +   must be called again.     Note: this function's sub-functions call failf()  */ -static CURLcode connected_proxy(struct connectdata *conn, int sockindex) +static CURLcode connect_SOCKS(struct connectdata *conn, int sockindex, +                              bool *done)  {    CURLcode result = CURLE_OK; +  infof(conn->data, "connect_SOCKS is called [socks state %d]\n", +        conn->cnnct.state); +    if(conn->bits.socksproxy) {  #ifndef CURL_DISABLE_PROXY      /* for the secondary socket (FTP), use the "connect to host"       * but ignore the "connect to port" (use the secondary port)       */ -    const char * const host = conn->bits.httpproxy ? -                              conn->http_proxy.host.name : -                              conn->bits.conn_to_host ? -                              conn->conn_to_host.name : -                              sockindex == SECONDARYSOCKET ? -                              conn->secondaryhostname : conn->host.name; -    const int port = conn->bits.httpproxy ? (int)conn->http_proxy.port : -                     sockindex == SECONDARYSOCKET ? conn->secondary_port : -                     conn->bits.conn_to_port ? conn->conn_to_port : -                     conn->remote_port; -    conn->bits.socksproxy_connecting = TRUE; +    const char * const host = +      conn->bits.httpproxy ? +      conn->http_proxy.host.name : +      conn->bits.conn_to_host ? +      conn->conn_to_host.name : +      sockindex == SECONDARYSOCKET ? +      conn->secondaryhostname : conn->host.name; +    const int port = +      conn->bits.httpproxy ? (int)conn->http_proxy.port : +      sockindex == SECONDARYSOCKET ? conn->secondary_port : +      conn->bits.conn_to_port ? conn->conn_to_port : +      conn->remote_port;      switch(conn->socks_proxy.proxytype) {      case CURLPROXY_SOCKS5:      case CURLPROXY_SOCKS5_HOSTNAME:        result = Curl_SOCKS5(conn->socks_proxy.user, conn->socks_proxy.passwd, -                         host, port, sockindex, conn); +                           host, port, sockindex, conn, done);        break;      case CURLPROXY_SOCKS4:      case CURLPROXY_SOCKS4A:        result = Curl_SOCKS4(conn->socks_proxy.user, host, port, sockindex, -                           conn); +                           conn, done);        break;      default:        failf(conn->data, "unknown proxytype option given");        result = CURLE_COULDNT_CONNECT;      } /* switch proxytype */ -    conn->bits.socksproxy_connecting = FALSE;  #else    (void)sockindex;  #endif /* CURL_DISABLE_PROXY */    } +  else +    *done = TRUE; /* no SOCKS proxy, so consider us connected */    return result;  }  /* + * post_SOCKS() is called after a successful connect to the peer, which + * *could* be a SOCKS proxy + */ +static void post_SOCKS(struct connectdata *conn, +                       int sockindex, +                       bool *connected) +{ +  conn->bits.tcpconnect[sockindex] = TRUE; + +  *connected = TRUE; +  if(sockindex == FIRSTSOCKET) +    Curl_pgrsTime(conn->data, TIMER_CONNECT); /* connect done */ +  Curl_updateconninfo(conn, conn->sock[sockindex]); +  Curl_verboseconnect(conn); +} + +/*   * Curl_is_connected() checks if the socket has connected.   */ @@ -834,6 +858,14 @@ CURLcode Curl_is_connected(struct connectdata *conn,      return CURLE_OPERATION_TIMEDOUT;    } +  if(SOCKS_STATE(conn->cnnct.state)) { +    /* still doing SOCKS */ +    result = connect_SOCKS(conn, sockindex, connected); +    if(!result && *connected) +      post_SOCKS(conn, sockindex, connected); +    return result; +  } +    for(i = 0; i<2; i++) {      const int other = i ^ 1;      if(conn->tempsock[i] == CURL_SOCKET_BAD) @@ -900,18 +932,13 @@ CURLcode Curl_is_connected(struct connectdata *conn,            conn->tempsock[other] = CURL_SOCKET_BAD;          } -        /* see if we need to do any proxy magic first once we connected */ -        result = connected_proxy(conn, sockindex); -        if(result) +        /* see if we need to kick off any SOCKS proxy magic once we +           connected */ +        result = connect_SOCKS(conn, sockindex, connected); +        if(result || !*connected)            return result; -        conn->bits.tcpconnect[sockindex] = TRUE; - -        *connected = TRUE; -        if(sockindex == FIRSTSOCKET) -          Curl_pgrsTime(data, TIMER_CONNECT); /* connect done */ -        Curl_updateconninfo(conn, conn->sock[sockindex]); -        Curl_verboseconnect(conn); +        post_SOCKS(conn, sockindex, connected);          return CURLE_OK;        } @@ -55,7 +55,6 @@  #include "transfer.h"  #include "escape.h"  #include "http.h" /* for HTTP proxy tunnel stuff */ -#include "socks.h"  #include "ftp.h"  #include "fileinfo.h"  #include "ftplistparser.h" @@ -78,6 +77,7 @@  #include "warnless.h"  #include "http_proxy.h"  #include "non-ascii.h" +#include "socks.h"  /* The last 3 #include files should be in this order */  #include "curl_printf.h"  #include "curl_memory.h" @@ -810,6 +810,9 @@ static int ftp_domore_getsock(struct connectdata *conn, curl_socket_t *socks)     * handle ordinary commands.     */ +  if(SOCKS_STATE(conn->cnnct.state)) +    return Curl_SOCKS_getsock(conn, socks, SECONDARYSOCKET); +    if(FTP_STOP == ftpc->state) {      int bits = GETSOCK_READSOCK(0); @@ -919,7 +922,7 @@ static CURLcode ftp_state_use_port(struct connectdata *conn,    struct sockaddr_in6 * const sa6 = (void *)sa;  #endif    static const char mode[][5] = { "EPRT", "PORT" }; -  int rc; +  enum resolve_t rc;    int error;    char *host = NULL;    char *string_ftpport = data->set.str[STRING_FTPPORT]; @@ -1794,7 +1797,7 @@ static CURLcode ftp_state_pasv_resp(struct connectdata *conn,    CURLcode result;    struct Curl_easy *data = conn->data;    struct Curl_dns_entry *addr = NULL; -  int rc; +  enum resolve_t rc;    unsigned short connectport; /* the local port connect() should use! */    char *str = &data->state.buffer[4];  /* start on the first letter */ diff --git a/lib/hostip.c b/lib/hostip.c index 9c0464ceb..c0feb79fb 100644 --- a/lib/hostip.c +++ b/lib/hostip.c @@ -483,16 +483,16 @@ Curl_cache_addr(struct Curl_easy *data,   * CURLRESOLV_PENDING  (1) = waiting for response, no pointer   */ -int Curl_resolv(struct connectdata *conn, -                const char *hostname, -                int port, -                bool allowDOH, -                struct Curl_dns_entry **entry) +enum resolve_t Curl_resolv(struct connectdata *conn, +                           const char *hostname, +                           int port, +                           bool allowDOH, +                           struct Curl_dns_entry **entry)  {    struct Curl_dns_entry *dns = NULL;    struct Curl_easy *data = conn->data;    CURLcode result; -  int rc = CURLRESOLV_ERROR; /* default to failure */ +  enum resolve_t rc = CURLRESOLV_ERROR; /* default to failure */    *entry = NULL; @@ -642,11 +642,11 @@ RETSIGTYPE alarmfunc(int sig)   * CURLRESOLV_PENDING  (1) = waiting for response, no pointer   */ -int Curl_resolv_timeout(struct connectdata *conn, -                        const char *hostname, -                        int port, -                        struct Curl_dns_entry **entry, -                        timediff_t timeoutms) +enum resolve_t Curl_resolv_timeout(struct connectdata *conn, +                                   const char *hostname, +                                   int port, +                                   struct Curl_dns_entry **entry, +                                   timediff_t timeoutms)  {  #ifdef USE_ALARM_TIMEOUT  #ifdef HAVE_SIGACTION @@ -662,7 +662,7 @@ int Curl_resolv_timeout(struct connectdata *conn,    volatile unsigned int prev_alarm = 0;    struct Curl_easy *data = conn->data;  #endif /* USE_ALARM_TIMEOUT */ -  int rc; +  enum resolve_t rc;    *entry = NULL; diff --git a/lib/hostip.h b/lib/hostip.h index a1c343ad7..baf1e5860 100644 --- a/lib/hostip.h +++ b/lib/hostip.h @@ -79,18 +79,21 @@ struct Curl_dns_entry {   * use, or we'll leak memory!   */  /* return codes */ -#define CURLRESOLV_TIMEDOUT -2 -#define CURLRESOLV_ERROR    -1 -#define CURLRESOLV_RESOLVED  0 -#define CURLRESOLV_PENDING   1 -int Curl_resolv(struct connectdata *conn, -                const char *hostname, -                int port, -                bool allowDOH, -                struct Curl_dns_entry **dnsentry); -int Curl_resolv_timeout(struct connectdata *conn, const char *hostname, -                        int port, struct Curl_dns_entry **dnsentry, -                        timediff_t timeoutms); +enum resolve_t { +  CURLRESOLV_TIMEDOUT = -2, +  CURLRESOLV_ERROR    = -1, +  CURLRESOLV_RESOLVED =  0, +  CURLRESOLV_PENDING  =  1 +}; +enum resolve_t Curl_resolv(struct connectdata *conn, +                           const char *hostname, +                           int port, +                           bool allowDOH, +                           struct Curl_dns_entry **dnsentry); +enum resolve_t Curl_resolv_timeout(struct connectdata *conn, +                                   const char *hostname, int port, +                                   struct Curl_dns_entry **dnsentry, +                                   timediff_t timeoutms);  #ifdef CURLRES_IPV6  /* diff --git a/lib/multi.c b/lib/multi.c index 51154d6b2..ef86f7c22 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -47,6 +47,7 @@  #include "http_proxy.h"  #include "http2.h"  #include "socketpair.h" +#include "socks.h"  /* The last 3 #include files should be in this order */  #include "curl_printf.h"  #include "curl_memory.h" @@ -856,6 +857,9 @@ static int waitconnect_getsock(struct connectdata *conn,      return Curl_ssl_getsock(conn, sock);  #endif +  if(SOCKS_STATE(conn->cnnct.state)) +    return Curl_SOCKS_getsock(conn, sock, FIRSTSOCKET); +    for(i = 0; i<2; i++) {      if(conn->tempsock[i] != CURL_SOCKET_BAD) {        sock[s] = conn->tempsock[i]; diff --git a/lib/sendf.c b/lib/sendf.c index 62071c4f2..51bbca75e 100644 --- a/lib/sendf.c +++ b/lib/sendf.c @@ -692,19 +692,20 @@ CURLcode Curl_read_plain(curl_socket_t sockfd,    ssize_t nread = sread(sockfd, buf, bytesfromsocket);    if(-1 == nread) { -    int err = SOCKERRNO; -    int return_error; +    const int err = SOCKERRNO; +    const bool return_error =  #ifdef USE_WINSOCK -    return_error = WSAEWOULDBLOCK == err; +      WSAEWOULDBLOCK == err  #else -    return_error = EWOULDBLOCK == err || EAGAIN == err || EINTR == err; +      EWOULDBLOCK == err || EAGAIN == err || EINTR == err  #endif +      ; +    *n = 0; /* no data returned */      if(return_error)        return CURLE_AGAIN;      return CURLE_RECV_ERROR;    } -  /* we only return number of bytes read when we return OK */    *n = nread;    return CURLE_OK;  } diff --git a/lib/socks.c b/lib/socks.c index 6ae98184d..ba05602be 100644 --- a/lib/socks.c +++ b/lib/socks.c @@ -5,7 +5,7 @@   *                            | (__| |_| |  _ <| |___   *                             \___|\___/|_| \_\_____|   * - * Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) 1998 - 2020, Daniel Stenberg, <daniel@haxx.se>, et al.   *   * This software is licensed as described in the file COPYING, which   * you should have received as part of this distribution. The terms @@ -37,18 +37,19 @@  #include "connect.h"  #include "timeval.h"  #include "socks.h" +#include "multiif.h" /* for getsock macros */  /* The last 3 #include files should be in this order */  #include "curl_printf.h"  #include "curl_memory.h"  #include "memdebug.h" +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)  /*   * Helper read-from-socket functions. Does the same as Curl_read() but it   * blocks until all bytes amount of buffersize will be read. No more, no less.   * - * This is STUPID BLOCKING behaviour which we frown upon, but right now this - * is what we have... + * This is STUPID BLOCKING behavior. Only used by the SOCKS GSSAPI functions.   */  int Curl_blockread_all(struct connectdata *conn, /* connection data */                         curl_socket_t sockfd,     /* read from this socket */ @@ -94,6 +95,81 @@ int Curl_blockread_all(struct connectdata *conn, /* connection data */    }    return result;  } +#endif + +#ifndef DEBUGBUILD +#define sxstate(x,y) socksstate(x,y) +#else +#define sxstate(x,y) socksstate(x,y, __LINE__) +#endif + + +/* always use this function to change state, to make debugging easier */ +static void socksstate(struct connectdata *conn, +                       enum connect_t state +#ifdef DEBUGBUILD +                       , int lineno +#endif +) +{ +  enum connect_t oldstate = conn->cnnct.state; +#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) +  /* synced with the state list in urldata.h */ +  static const char * const statename[] = { +    "INIT", +    "SOCKS_INIT", +    "SOCKS_SEND", +    "SOCKS_READ_INIT", +    "SOCKS_READ", +    "GSSAPI_INIT", +    "AUTH_INIT", +    "AUTH_SEND", +    "AUTH_READ", +    "REQ_INIT", +    "RESOLVING", +    "RESOLVED", +    "RESOLVE_REMOTE", +    "REQ_SEND", +    "REQ_SENDING", +    "REQ_READ", +    "REQ_READ_MORE", +    "DONE" +  }; +#endif + +  if(oldstate == state) +    /* don't bother when the new state is the same as the old state */ +    return; + +  conn->cnnct.state = state; + +#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) +  infof(conn->data, +        "SXSTATE: %s => %s conn %p; line %d\n", +        statename[oldstate], statename[conn->cnnct.state], conn, +        lineno); +#endif +} + +int Curl_SOCKS_getsock(struct connectdata *conn, curl_socket_t *sock, +                       int sockindex) +{ +  int rc = 0; +  sock[0] = conn->sock[sockindex]; +  switch(conn->cnnct.state) { +  case CONNECT_RESOLVING: +  case CONNECT_SOCKS_READ: +  case CONNECT_AUTH_READ: +  case CONNECT_REQ_READ: +  case CONNECT_REQ_READ_MORE: +    rc = GETSOCK_READSOCK(0); +    break; +  default: +    rc = GETSOCK_WRITESOCK(0); +    break; +  } +  return rc; +}  /*  * This function logs in to a SOCKS4 proxy and sends the specifics to the final @@ -110,62 +186,89 @@ CURLcode Curl_SOCKS4(const char *proxy_user,                       const char *hostname,                       int remote_port,                       int sockindex, -                     struct connectdata *conn) +                     struct connectdata *conn, +                     bool *done)  {    const bool protocol4a =      (conn->socks_proxy.proxytype == CURLPROXY_SOCKS4A) ? TRUE : FALSE; -#define SOCKS4REQLEN 262 -  unsigned char socksreq[SOCKS4REQLEN]; /* room for SOCKS4 request incl. user -                                           id */ -  CURLcode code; -  curl_socket_t sock = conn->sock[sockindex]; +  unsigned char *socksreq = &conn->cnnct.socksreq[0]; +  CURLcode result; +  curl_socket_t sockfd = conn->sock[sockindex];    struct Curl_easy *data = conn->data; +  struct connstate *sx = &conn->cnnct; +  struct Curl_dns_entry *dns = NULL; +  ssize_t actualread; +  ssize_t written; -  if(Curl_timeleft(data, NULL, TRUE) < 0) { -    /* time-out, bail out, go home */ -    failf(data, "Connection time-out"); -    return CURLE_OPERATION_TIMEDOUT; -  } - -  if(conn->bits.httpproxy) -    infof(conn->data, "SOCKS4%s: connecting to HTTP proxy %s port %d\n", -          protocol4a ? "a" : "", hostname, remote_port); - -  (void)curlx_nonblock(sock, FALSE); +  if(!SOCKS_STATE(sx->state) && !*done) +    sxstate(conn, CONNECT_SOCKS_INIT); -  infof(data, "SOCKS4 communication to %s:%d\n", hostname, remote_port); +  switch(sx->state) { +  case CONNECT_SOCKS_INIT: +    if(conn->bits.httpproxy) +      infof(conn->data, "SOCKS4%s: connecting to HTTP proxy %s port %d\n", +            protocol4a ? "a" : "", hostname, remote_port); -  /* -   * Compose socks4 request -   * -   * Request format -   * -   *     +----+----+----+----+----+----+----+----+----+----+....+----+ -   *     | VN | CD | DSTPORT |      DSTIP        | USERID       |NULL| -   *     +----+----+----+----+----+----+----+----+----+----+....+----+ -   * # of bytes:  1    1      2              4           variable       1 -   */ +    infof(data, "SOCKS4 communication to %s:%d\n", hostname, remote_port); -  socksreq[0] = 4; /* version (SOCKS4) */ -  socksreq[1] = 1; /* connect */ -  socksreq[2] = (unsigned char)((remote_port >> 8) & 0xff); /* PORT MSB */ -  socksreq[3] = (unsigned char)(remote_port & 0xff);        /* PORT LSB */ - -  /* DNS resolve only for SOCKS4, not SOCKS4a */ -  if(!protocol4a) { -    struct Curl_dns_entry *dns; -    Curl_addrinfo *hp = NULL; -    int rc; +    /* +     * Compose socks4 request +     * +     * Request format +     * +     *     +----+----+----+----+----+----+----+----+----+----+....+----+ +     *     | VN | CD | DSTPORT |      DSTIP        | USERID       |NULL| +     *     +----+----+----+----+----+----+----+----+----+----+....+----+ +     * # of bytes:  1    1      2              4           variable       1 +     */ -    rc = Curl_resolv(conn, hostname, remote_port, FALSE, &dns); +    socksreq[0] = 4; /* version (SOCKS4) */ +    socksreq[1] = 1; /* connect */ +    socksreq[2] = (unsigned char)((remote_port >> 8) & 0xff); /* PORT MSB */ +    socksreq[3] = (unsigned char)(remote_port & 0xff);        /* PORT LSB */ + +    /* DNS resolve only for SOCKS4, not SOCKS4a */ +    if(!protocol4a) { +      enum resolve_t rc = +        Curl_resolv(conn, hostname, remote_port, FALSE, &dns); + +      if(rc == CURLRESOLV_ERROR) +        return CURLE_COULDNT_RESOLVE_PROXY; +      else if(rc == CURLRESOLV_PENDING) { +        sxstate(conn, CONNECT_RESOLVING); +        infof(data, "SOCKS4 non-blocking resolve of %s\n", hostname); +        return CURLE_OK; +      } +      sxstate(conn, CONNECT_RESOLVED); +      goto CONNECT_RESOLVED; +    } -    if(rc == CURLRESOLV_ERROR) -      return CURLE_COULDNT_RESOLVE_PROXY; +    /* socks4a doesn't resolve anything locally */ +    sxstate(conn, CONNECT_REQ_INIT); +    goto CONNECT_REQ_INIT; -    if(rc == CURLRESOLV_PENDING) -      /* ignores the return code, but 'dns' remains NULL on failure */ -      (void)Curl_resolver_wait_resolv(conn, &dns); +  case CONNECT_RESOLVING: +    /* check if we have the name resolved by now */ +    dns = Curl_fetch_addr(conn, hostname, (int)conn->port); +    if(dns) { +#ifdef CURLRES_ASYNCH +      conn->async.dns = dns; +      conn->async.done = TRUE; +#endif +      infof(data, "Hostname '%s' was found\n", hostname); +      sxstate(conn, CONNECT_RESOLVED); +    } +    else { +      result = Curl_resolv_check(data->conn, &dns); +      /* stay in the state or error out */ +      return result; +    } +    /* FALLTHROUGH */ +  CONNECT_RESOLVED: +  case CONNECT_RESOLVED: { +    Curl_addrinfo *hp = NULL; +    char buf[64];      /*       * We cannot use 'hostent' as a struct that Curl_resolv() returns.  It       * returns a Curl_addrinfo pointer that may not always look the same. @@ -173,7 +276,6 @@ CURLcode Curl_SOCKS4(const char *proxy_user,      if(dns)        hp = dns->addr;      if(hp) { -      char buf[64];        Curl_printable_address(hp, buf, sizeof(buf));        if(hp->ai_family == AF_INET) { @@ -189,7 +291,6 @@ CURLcode Curl_SOCKS4(const char *proxy_user,        }        else {          hp = NULL; /* fail! */ -          failf(data, "SOCKS4 connection to %s not supported\n", buf);        } @@ -201,149 +302,166 @@ CURLcode Curl_SOCKS4(const char *proxy_user,        return CURLE_COULDNT_RESOLVE_HOST;      }    } - -  /* -   * This is currently not supporting "Identification Protocol (RFC1413)". -   */ -  socksreq[8] = 0; /* ensure empty userid is NUL-terminated */ -  if(proxy_user) { -    size_t plen = strlen(proxy_user); -    if(plen >= sizeof(socksreq) - 8) { -      failf(data, "Too long SOCKS proxy name, can't use!\n"); -      return CURLE_COULDNT_CONNECT; +    /* FALLTHROUGH */ +  CONNECT_REQ_INIT: +  case CONNECT_REQ_INIT: +    /* +     * This is currently not supporting "Identification Protocol (RFC1413)". +     */ +    socksreq[8] = 0; /* ensure empty userid is NUL-terminated */ +    if(proxy_user) { +      size_t plen = strlen(proxy_user); +      if(plen >= sizeof(sx->socksreq) - 8) { +        failf(data, "Too long SOCKS proxy name, can't use!\n"); +        return CURLE_COULDNT_CONNECT; +      } +      /* copy the proxy name WITH trailing zero */ +      memcpy(socksreq + 8, proxy_user, plen + 1);      } -    /* copy the proxy name WITH trailing zero */ -    memcpy(socksreq + 8, proxy_user, plen + 1); -  } -  /* -   * Make connection -   */ -  { -    int result; -    ssize_t actualread; -    ssize_t written; -    ssize_t hostnamelen = 0; -    ssize_t packetsize = 9 + -      strlen((char *)socksreq + 8); /* size including NUL */ - -    /* If SOCKS4a, set special invalid IP address 0.0.0.x */ -    if(protocol4a) { -      socksreq[4] = 0; -      socksreq[5] = 0; -      socksreq[6] = 0; -      socksreq[7] = 1; -      /* If still enough room in buffer, also append hostname */ -      hostnamelen = (ssize_t)strlen(hostname) + 1; /* length including NUL */ -      if(packetsize + hostnamelen <= SOCKS4REQLEN) -        strcpy((char *)socksreq + packetsize, hostname); -      else -        hostnamelen = 0; /* Flag: hostname did not fit in buffer */ +    /* +     * Make connection +     */ +    { +      ssize_t packetsize = 9 + +        strlen((char *)socksreq + 8); /* size including NUL */ + +      /* If SOCKS4a, set special invalid IP address 0.0.0.x */ +      if(protocol4a) { +        ssize_t hostnamelen = 0; +        socksreq[4] = 0; +        socksreq[5] = 0; +        socksreq[6] = 0; +        socksreq[7] = 1; +        /* append hostname */ +        hostnamelen = (ssize_t)strlen(hostname) + 1; /* length including NUL */ +        if(hostnamelen <= 255) +          strcpy((char *)socksreq + packetsize, hostname); +        else { +          failf(data, "SOCKS4: too long host name"); +          return CURLE_COULDNT_CONNECT; +        } +        packetsize += hostnamelen; +      } +      sx->outp = socksreq; +      sx->outstanding = packetsize; +      sxstate(conn, CONNECT_REQ_SENDING);      } - +    /* FALLTHROUGH */ +  case CONNECT_REQ_SENDING:      /* Send request */ -    code = Curl_write_plain(conn, sock, (char *)socksreq, -                            packetsize + hostnamelen, -                            &written); -    if(code || (written != packetsize + hostnamelen)) { +    result = Curl_write_plain(conn, sockfd, (char *)sx->outp, +                              sx->outstanding, &written); +    if(result && (CURLE_AGAIN != result)) {        failf(data, "Failed to send SOCKS4 connect request.");        return CURLE_COULDNT_CONNECT;      } -    if(protocol4a && hostnamelen == 0) { -      /* SOCKS4a with very long hostname - send that name separately */ -      hostnamelen = (ssize_t)strlen(hostname) + 1; -      code = Curl_write_plain(conn, sock, (char *)hostname, hostnamelen, -                              &written); -      if(code || (written != hostnamelen)) { -        failf(data, "Failed to send SOCKS4 connect request."); -        return CURLE_COULDNT_CONNECT; -      } +    if(written != sx->outstanding) { +      /* not done, remain in state */ +      sx->outstanding -= written; +      sx->outp += written; +      return CURLE_OK;      } -    packetsize = 8; /* receive data size */ +    /* done sending! */ +    sx->outstanding = 8; /* receive data size */ +    sx->outp = socksreq; +    sxstate(conn, CONNECT_SOCKS_READ); +    /* FALLTHROUGH */ +  case CONNECT_SOCKS_READ:      /* Receive response */ -    result = Curl_blockread_all(conn, sock, (char *)socksreq, packetsize, -                                &actualread); -    if(result || (actualread != packetsize)) { -      failf(data, "Failed to receive SOCKS4 connect request ack."); +    result = Curl_read_plain(sockfd, (char *)sx->outp, +                             sx->outstanding, &actualread); +    if(result && (CURLE_AGAIN != result)) { +      failf(data, "SOCKS4: Failed receiving connect request ack: %s", +            curl_easy_strerror(result));        return CURLE_COULDNT_CONNECT;      } - -    /* -     * Response format -     * -     *     +----+----+----+----+----+----+----+----+ -     *     | VN | CD | DSTPORT |      DSTIP        | -     *     +----+----+----+----+----+----+----+----+ -     * # of bytes:  1    1      2              4 -     * -     * VN is the version of the reply code and should be 0. CD is the result -     * code with one of the following values: -     * -     * 90: request granted -     * 91: request rejected or failed -     * 92: request rejected because SOCKS server cannot connect to -     *     identd on the client -     * 93: request rejected because the client program and identd -     *     report different user-ids -     */ - -    /* wrong version ? */ -    if(socksreq[0] != 0) { -      failf(data, -            "SOCKS4 reply has wrong version, version should be 0."); -      return CURLE_COULDNT_CONNECT; +    else if(actualread != sx->outstanding) { +      /* remain in reading state */ +      sx->outstanding -= actualread; +      sx->outp += actualread; +      return CURLE_OK;      } +    sxstate(conn, CONNECT_DONE); +    break; +  default: /* lots of unused states in SOCKS4 */ +    break; +  } -    /* Result */ -    switch(socksreq[1]) { -    case 90: -      infof(data, "SOCKS4%s request granted.\n", protocol4a?"a":""); -      break; -    case 91: -      failf(data, -            "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" -            ", request rejected or failed.", -            (unsigned char)socksreq[4], (unsigned char)socksreq[5], -            (unsigned char)socksreq[6], (unsigned char)socksreq[7], -            (((unsigned char)socksreq[2] << 8) | (unsigned char)socksreq[3]), -            (unsigned char)socksreq[1]); -      return CURLE_COULDNT_CONNECT; -    case 92: -      failf(data, -            "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" -            ", request rejected because SOCKS server cannot connect to " -            "identd on the client.", -            (unsigned char)socksreq[4], (unsigned char)socksreq[5], -            (unsigned char)socksreq[6], (unsigned char)socksreq[7], -            (((unsigned char)socksreq[2] << 8) | (unsigned char)socksreq[3]), -            (unsigned char)socksreq[1]); -      return CURLE_COULDNT_CONNECT; -    case 93: -      failf(data, -            "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" -            ", request rejected because the client program and identd " -            "report different user-ids.", -            (unsigned char)socksreq[4], (unsigned char)socksreq[5], -            (unsigned char)socksreq[6], (unsigned char)socksreq[7], -            (((unsigned char)socksreq[2] << 8) | (unsigned char)socksreq[3]), -            (unsigned char)socksreq[1]); -      return CURLE_COULDNT_CONNECT; -    default: -      failf(data, -            "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" -            ", Unknown.", -            (unsigned char)socksreq[4], (unsigned char)socksreq[5], -            (unsigned char)socksreq[6], (unsigned char)socksreq[7], -            (((unsigned char)socksreq[2] << 8) | (unsigned char)socksreq[3]), -            (unsigned char)socksreq[1]); -      return CURLE_COULDNT_CONNECT; -    } +  /* +   * Response format +   * +   *     +----+----+----+----+----+----+----+----+ +   *     | VN | CD | DSTPORT |      DSTIP        | +   *     +----+----+----+----+----+----+----+----+ +   * # of bytes:  1    1      2              4 +   * +   * VN is the version of the reply code and should be 0. CD is the result +   * code with one of the following values: +   * +   * 90: request granted +   * 91: request rejected or failed +   * 92: request rejected because SOCKS server cannot connect to +   *     identd on the client +   * 93: request rejected because the client program and identd +   *     report different user-ids +   */ + +  /* wrong version ? */ +  if(socksreq[0] != 0) { +    failf(data, +          "SOCKS4 reply has wrong version, version should be 0."); +    return CURLE_COULDNT_CONNECT;    } -  (void)curlx_nonblock(sock, TRUE); +  /* Result */ +  switch(socksreq[1]) { +  case 90: +    infof(data, "SOCKS4%s request granted.\n", protocol4a?"a":""); +    break; +  case 91: +    failf(data, +          "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" +          ", request rejected or failed.", +          (unsigned char)socksreq[4], (unsigned char)socksreq[5], +          (unsigned char)socksreq[6], (unsigned char)socksreq[7], +          (((unsigned char)socksreq[2] << 8) | (unsigned char)socksreq[3]), +          (unsigned char)socksreq[1]); +    return CURLE_COULDNT_CONNECT; +  case 92: +    failf(data, +          "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" +          ", request rejected because SOCKS server cannot connect to " +          "identd on the client.", +          (unsigned char)socksreq[4], (unsigned char)socksreq[5], +          (unsigned char)socksreq[6], (unsigned char)socksreq[7], +          (((unsigned char)socksreq[2] << 8) | (unsigned char)socksreq[3]), +          (unsigned char)socksreq[1]); +    return CURLE_COULDNT_CONNECT; +  case 93: +    failf(data, +          "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" +          ", request rejected because the client program and identd " +          "report different user-ids.", +          (unsigned char)socksreq[4], (unsigned char)socksreq[5], +          (unsigned char)socksreq[6], (unsigned char)socksreq[7], +          (((unsigned char)socksreq[2] << 8) | (unsigned char)socksreq[3]), +          (unsigned char)socksreq[1]); +    return CURLE_COULDNT_CONNECT; +  default: +    failf(data, +          "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" +          ", Unknown.", +          (unsigned char)socksreq[4], (unsigned char)socksreq[5], +          (unsigned char)socksreq[6], (unsigned char)socksreq[7], +          (((unsigned char)socksreq[2] << 8) | (unsigned char)socksreq[3]), +          (unsigned char)socksreq[1]); +    return CURLE_COULDNT_CONNECT; +  } +  *done = TRUE;    return CURLE_OK; /* Proxy was successful! */  } @@ -356,7 +474,8 @@ CURLcode Curl_SOCKS5(const char *proxy_user,                       const char *hostname,                       int remote_port,                       int sockindex, -                     struct connectdata *conn) +                     struct connectdata *conn, +                     bool *done)  {    /*      According to the RFC1928, section "6.  Replies". This is what a SOCK5 @@ -374,141 +493,158 @@ CURLcode Curl_SOCKS5(const char *proxy_user,      o  REP    Reply field:      o  X'00' succeeded    */ -#define REQUEST_BUFSIZE 600  /* room for large user/pw (255 max each) */ -  unsigned char socksreq[REQUEST_BUFSIZE]; -  char dest[REQUEST_BUFSIZE] = "unknown";  /* printable hostname:port */ +  unsigned char *socksreq = &conn->cnnct.socksreq[0]; +  char dest[256] = "unknown";  /* printable hostname:port */    int idx;    ssize_t actualread;    ssize_t written; -  int result; -  CURLcode code; -  curl_socket_t sock = conn->sock[sockindex]; +  CURLcode result; +  curl_socket_t sockfd = conn->sock[sockindex];    struct Curl_easy *data = conn->data; -  timediff_t timeout;    bool socks5_resolve_local =      (conn->socks_proxy.proxytype == CURLPROXY_SOCKS5) ? TRUE : FALSE;    const size_t hostname_len = strlen(hostname);    ssize_t len = 0;    const unsigned long auth = data->set.socks5auth;    bool allow_gssapi = FALSE; +  struct connstate *sx = &conn->cnnct; +  struct Curl_dns_entry *dns = NULL; + +  if(!SOCKS_STATE(sx->state) && !*done) +    sxstate(conn, CONNECT_SOCKS_INIT); + +  switch(sx->state) { +  case CONNECT_SOCKS_INIT: +    if(conn->bits.httpproxy) +      infof(conn->data, "SOCKS5: connecting to HTTP proxy %s port %d\n", +            hostname, remote_port); + +    /* RFC1928 chapter 5 specifies max 255 chars for domain name in packet */ +    if(!socks5_resolve_local && hostname_len > 255) { +      infof(conn->data, "SOCKS5: server resolving disabled for hostnames of " +            "length > 255 [actual len=%zu]\n", hostname_len); +      socks5_resolve_local = TRUE; +    } -  if(conn->bits.httpproxy) -    infof(conn->data, "SOCKS5: connecting to HTTP proxy %s port %d\n", -          hostname, remote_port); - -  /* RFC1928 chapter 5 specifies max 255 chars for domain name in packet */ -  if(!socks5_resolve_local && hostname_len > 255) { -    infof(conn->data, "SOCKS5: server resolving disabled for hostnames of " -          "length > 255 [actual len=%zu]\n", hostname_len); -    socks5_resolve_local = TRUE; -  } - -  /* get timeout */ -  timeout = Curl_timeleft(data, NULL, TRUE); - -  if(timeout < 0) { -    /* time-out, bail out, go home */ -    failf(data, "Connection time-out"); -    return CURLE_OPERATION_TIMEDOUT; -  } - -  (void)curlx_nonblock(sock, TRUE); - -  /* wait until socket gets connected */ -  result = SOCKET_WRITABLE(sock, timeout); - -  if(-1 == result) { -    failf(conn->data, "SOCKS5: no connection here"); -    return CURLE_COULDNT_CONNECT; -  } -  if(0 == result) { -    failf(conn->data, "SOCKS5: connection timeout"); -    return CURLE_OPERATION_TIMEDOUT; -  } - -  if(result & CURL_CSELECT_ERR) { -    failf(conn->data, "SOCKS5: error occurred during connection"); -    return CURLE_COULDNT_CONNECT; -  } - -  if(auth & ~(CURLAUTH_BASIC | CURLAUTH_GSSAPI)) -    infof(conn->data, -        "warning: unsupported value passed to CURLOPT_SOCKS5_AUTH: %lu\n", -        auth); -  if(!(auth & CURLAUTH_BASIC)) -    /* disable username/password auth */ -    proxy_user = NULL; +    if(auth & ~(CURLAUTH_BASIC | CURLAUTH_GSSAPI)) +      infof(conn->data, +            "warning: unsupported value passed to CURLOPT_SOCKS5_AUTH: %lu\n", +            auth); +    if(!(auth & CURLAUTH_BASIC)) +      /* disable username/password auth */ +      proxy_user = NULL;  #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) -  if(auth & CURLAUTH_GSSAPI) -    allow_gssapi = TRUE; +    if(auth & CURLAUTH_GSSAPI) +      allow_gssapi = TRUE;  #endif -  idx = 0; -  socksreq[idx++] = 5;   /* version */ -  idx++;                 /* reserve for the number of authentication methods */ -  socksreq[idx++] = 0;   /* no authentication */ -  if(allow_gssapi) -    socksreq[idx++] = 1; /* GSS-API */ -  if(proxy_user) -    socksreq[idx++] = 2; /* username/password */ -  /* write the number of authentication methods */ -  socksreq[1] = (unsigned char) (idx - 2); - -  (void)curlx_nonblock(sock, FALSE); - -  infof(data, "SOCKS5 communication to %s:%d\n", hostname, remote_port); - -  code = Curl_write_plain(conn, sock, (char *)socksreq, (2 + (int)socksreq[1]), -                          &written); -  if(code || (written != (2 + (int)socksreq[1]))) { -    failf(data, "Unable to send initial SOCKS5 request."); -    return CURLE_COULDNT_CONNECT; -  } - -  (void)curlx_nonblock(sock, TRUE); - -  result = SOCKET_READABLE(sock, timeout); - -  if(-1 == result) { -    failf(conn->data, "SOCKS5 nothing to read"); -    return CURLE_COULDNT_CONNECT; -  } -  if(0 == result) { -    failf(conn->data, "SOCKS5 read timeout"); -    return CURLE_OPERATION_TIMEDOUT; -  } - -  if(result & CURL_CSELECT_ERR) { -    failf(conn->data, "SOCKS5 read error occurred"); -    return CURLE_RECV_ERROR; -  } - -  (void)curlx_nonblock(sock, FALSE); - -  result = Curl_blockread_all(conn, sock, (char *)socksreq, 2, &actualread); -  if(result || (actualread != 2)) { -    failf(data, "Unable to receive initial SOCKS5 response."); -    return CURLE_COULDNT_CONNECT; -  } - -  if(socksreq[0] != 5) { -    failf(data, "Received invalid version in initial SOCKS5 response."); -    return CURLE_COULDNT_CONNECT; -  } -  if(socksreq[1] == 0) { -    /* Nothing to do, no authentication needed */ -    ; -  } +    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(proxy_user) +      socksreq[idx++] = 2; /* username/password */ +    /* write the number of authentication methods */ +    socksreq[1] = (unsigned char) (idx - 2); + +    result = Curl_write_plain(conn, sockfd, (char *)socksreq, idx, &written); +    if(result && (CURLE_AGAIN != result)) { +      failf(data, "Unable to send initial SOCKS5 request."); +      return CURLE_COULDNT_CONNECT; +    } +    if(written != idx) { +      sxstate(conn, CONNECT_SOCKS_SEND); +      sx->outstanding = idx - written; +      sx->outp = &socksreq[written]; +      return CURLE_OK; +    } +    sxstate(conn, CONNECT_SOCKS_READ); +    goto CONNECT_SOCKS_READ_INIT; +  case CONNECT_SOCKS_SEND: +    result = Curl_write_plain(conn, sockfd, (char *)sx->outp, +                              sx->outstanding, &written); +    if(result && (CURLE_AGAIN != result)) { +      failf(data, "Unable to send initial SOCKS5 request."); +      return CURLE_COULDNT_CONNECT; +    } +    if(written != sx->outstanding) { +      /* not done, remain in state */ +      sx->outstanding -= written; +      sx->outp += written; +      return CURLE_OK; +    } +    /* FALLTHROUGH */ +  CONNECT_SOCKS_READ_INIT: +  case CONNECT_SOCKS_READ_INIT: +    sx->outstanding = 2; /* expect two bytes */ +    sx->outp = socksreq; /* store it here */ +    /* FALLTHROUGH */ +  case CONNECT_SOCKS_READ: +    result = Curl_read_plain(sockfd, (char *)sx->outp, +                             sx->outstanding, &actualread); +    if(result && (CURLE_AGAIN != result)) { +      failf(data, "Unable to receive initial SOCKS5 response."); +      return CURLE_COULDNT_CONNECT; +    } +    else if(actualread != sx->outstanding) { +      /* remain in reading state */ +      sx->outstanding -= actualread; +      sx->outp += actualread; +      return CURLE_OK; +    } +    else if(socksreq[0] != 5) { +      failf(data, "Received invalid version in initial SOCKS5 response."); +      return CURLE_COULDNT_CONNECT; +    } +    else if(socksreq[1] == 0) { +      /* DONE! No authentication needed. Send request. */ +      sxstate(conn, CONNECT_REQ_INIT); +      goto CONNECT_REQ_INIT; +    } +    else if(socksreq[1] == 2) { +      /* regular name + password authentication */ +      sxstate(conn, CONNECT_AUTH_INIT); +      goto CONNECT_AUTH_INIT; +    }  #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) -  else if(allow_gssapi && (socksreq[1] == 1)) { -    code = Curl_SOCKS5_gssapi_negotiate(sockindex, conn); -    if(code) { -      failf(data, "Unable to negotiate SOCKS5 GSS-API context."); +    else if(allow_gssapi && (socksreq[1] == 1)) { +      sxstate(conn, CONNECT_GSSAPI_INIT); +      result = Curl_SOCKS5_gssapi_negotiate(sockindex, conn); +      if(result) { +        failf(data, "Unable to negotiate SOCKS5 GSS-API context."); +        return CURLE_COULDNT_CONNECT; +      } +    } +#endif +    else { +      /* error */ +      if(!allow_gssapi && (socksreq[1] == 1)) { +        failf(data, +              "SOCKS5 GSSAPI per-message authentication is not supported."); +        return CURLE_COULDNT_CONNECT; +      } +      else if(socksreq[1] == 255) { +        failf(data, "No authentication method was acceptable."); +        return CURLE_COULDNT_CONNECT; +      } +      failf(data, +            "Undocumented SOCKS5 mode attempted to be used by server.");        return CURLE_COULDNT_CONNECT;      } -  } +    break; +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) +  case CONNECT_GSSAPI_INIT: +    /* GSSAPI stuff done non-blocking */ +    break;  #endif -  else if(socksreq[1] == 2) { + +  default: /* do nothing! */ +    break; + +  CONNECT_AUTH_INIT: +  case CONNECT_AUTH_INIT: {      /* Needs user name and password */      size_t proxy_user_len, proxy_password_len;      if(proxy_user && proxy_password) { @@ -549,18 +685,41 @@ CURLcode Curl_SOCKS5(const char *proxy_user,        memcpy(socksreq + len, proxy_password, proxy_password_len);      }      len += proxy_password_len; - -    code = Curl_write_plain(conn, sock, (char *)socksreq, len, &written); -    if(code || (len != written)) { +    sxstate(conn, CONNECT_AUTH_SEND); +    sx->outstanding = len; +    sx->outp = socksreq; +  } +    /* FALLTHROUGH */ +  case CONNECT_AUTH_SEND: +    result = Curl_write_plain(conn, sockfd, (char *)sx->outp, +                              sx->outstanding, &written); +    if(result && (CURLE_AGAIN != result)) {        failf(data, "Failed to send SOCKS5 sub-negotiation request.");        return CURLE_COULDNT_CONNECT;      } - -    result = Curl_blockread_all(conn, sock, (char *)socksreq, 2, &actualread); -    if(result || (actualread != 2)) { +    if(sx->outstanding != written) { +      /* remain in state */ +      sx->outstanding -= written; +      sx->outp += written; +      return CURLE_OK; +    } +    sx->outp = socksreq; +    sx->outstanding = 2; +    sxstate(conn, CONNECT_AUTH_READ); +    /* FALLTHROUGH */ +  case CONNECT_AUTH_READ: +    result = Curl_read_plain(sockfd, (char *)sx->outp, +                             sx->outstanding, &actualread); +    if(result && (CURLE_AGAIN != result)) {        failf(data, "Unable to receive SOCKS5 sub-negotiation response.");        return CURLE_COULDNT_CONNECT;      } +    if(actualread != sx->outstanding) { +      /* remain in state */ +      sx->outstanding -= actualread; +      sx->outp += actualread; +      return CURLE_OK; +    }      /* ignore the first (VER) byte */      if(socksreq[1] != 0) { /* status */ @@ -570,209 +729,248 @@ CURLcode Curl_SOCKS5(const char *proxy_user,      }      /* Everything is good so far, user was authenticated! */ -  } -  else { -    /* error */ -    if(!allow_gssapi && (socksreq[1] == 1)) { -      failf(data, -            "SOCKS5 GSSAPI per-message authentication is not supported."); -      return CURLE_COULDNT_CONNECT; -    } -    if(socksreq[1] == 255) { -      if(!proxy_user || !*proxy_user) { -        failf(data, -              "No authentication method was acceptable. (It is quite likely" -              " that the SOCKS5 server wanted a username/password, since none" -              " was supplied to the server on this connection.)"); +    sxstate(conn, CONNECT_REQ_INIT); +    /* FALLTHROUGH */ +  CONNECT_REQ_INIT: +  case CONNECT_REQ_INIT: +    if(socks5_resolve_local) { +      enum resolve_t rc = Curl_resolv(conn, hostname, remote_port, +                                      FALSE, &dns); + +      if(rc == CURLRESOLV_ERROR) +        return CURLE_COULDNT_RESOLVE_HOST; + +      if(rc == CURLRESOLV_PENDING) { +        sxstate(conn, CONNECT_RESOLVING); +        return CURLE_OK;        } -      else { -        failf(data, "No authentication method was acceptable."); -      } -      return CURLE_COULDNT_CONNECT; +      sxstate(conn, CONNECT_RESOLVED); +      goto CONNECT_RESOLVED;      } -    else { -      failf(data, -            "Undocumented SOCKS5 mode attempted to be used by server."); -      return CURLE_COULDNT_CONNECT; -    } -  } +    goto 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) { -    socksreq[len++] = 3; /* ATYP: domain name = 3 */ -    socksreq[len++] = (char) hostname_len; /* address length */ -    memcpy(&socksreq[len], hostname, hostname_len); /* address str w/o NULL */ -    len += hostname_len; -    msnprintf(dest, sizeof(dest), "%s:%d", hostname, remote_port); -    infof(data, "SOCKS5 connect to %s (remotely resolved)\n", dest); -  } -  else { -    struct Curl_dns_entry *dns; -    Curl_addrinfo *hp = NULL; -    int rc = Curl_resolv(conn, hostname, remote_port, FALSE, &dns); - -    if(rc == CURLRESOLV_ERROR) -      return CURLE_COULDNT_RESOLVE_HOST; +  case CONNECT_RESOLVING: +    /* check if we have the name resolved by now */ +    dns = Curl_fetch_addr(conn, hostname, (int)conn->port); -    if(rc == CURLRESOLV_PENDING) { -      /* this requires that we're in "wait for resolve" state */ -      code = Curl_resolver_wait_resolv(conn, &dns); -      if(code) -        return code; +    if(dns) { +#ifdef CURLRES_ASYNCH +      conn->async.dns = dns; +      conn->async.done = TRUE; +#endif +      infof(data, "SOCKS5: hostname '%s' found\n", hostname);      } -    /* -     * We cannot use 'hostent' as a struct that Curl_resolv() returns.  It -     * returns a Curl_addrinfo pointer that may not always look the same. -     */ +    if(!dns) { +      result = Curl_resolv_check(data->conn, &dns); +      /* stay in the state or error out */ +      return result; +    } +    /* FALLTHROUGH */ +  CONNECT_RESOLVED: +  case CONNECT_RESOLVED: { +    Curl_addrinfo *hp = NULL;      if(dns)        hp = dns->addr; -    if(hp) { -      if(Curl_printable_address(hp, dest, sizeof(dest))) { -        size_t destlen = strlen(dest); -        msnprintf(dest + destlen, sizeof(dest) - destlen, ":%d", remote_port); -      } -      else { -        strcpy(dest, "unknown"); -      } - -      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]; -        } +    if(!hp) { +      failf(data, "Failed to resolve \"%s\" for SOCKS5 connect.", +            hostname); +      return CURLE_COULDNT_RESOLVE_HOST; +    } -        infof(data, "SOCKS5 connect to IPv4 %s (locally resolved)\n", dest); -      } -#ifdef ENABLE_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]; -        } +    if(Curl_printable_address(hp, dest, sizeof(dest))) { +      size_t destlen = strlen(dest); +      msnprintf(dest + destlen, sizeof(dest) - destlen, ":%d", remote_port); +    } +    else { +      strcpy(dest, "unknown"); +    } -        infof(data, "SOCKS5 connect to IPv6 %s (locally resolved)\n", 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];        } -#endif -      else { -        hp = NULL; /* fail! */ -        failf(data, "SOCKS5 connection to %s not supported\n", dest); +      infof(data, "SOCKS5 connect to IPv4 %s (locally resolved)\n", dest); +    } +#ifdef ENABLE_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];        } -      Curl_resolv_unlock(data, dns); /* not used anymore from now on */ +      infof(data, "SOCKS5 connect to IPv6 %s (locally resolved)\n", dest);      } -    if(!hp) { -      failf(data, "Failed to resolve \"%s\" for SOCKS5 connect.", -            hostname); -      return CURLE_COULDNT_RESOLVE_HOST; +#endif +    else { +      hp = NULL; /* fail! */ +      failf(data, "SOCKS5 connection to %s not supported\n", 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) { +      socksreq[len++] = 3; /* ATYP: domain name = 3 */ +      socksreq[len++] = (char) hostname_len; /* one byte address length */ +      memcpy(&socksreq[len], hostname, hostname_len); /* address w/o NULL */ +      len += hostname_len; +      infof(data, "SOCKS5 connect to %s:5d (remotely resolved)\n", +            hostname, remote_port); +    } +    /* FALLTHROUGH */ -  socksreq[len++] = (unsigned char)((remote_port >> 8) & 0xff); /* PORT MSB */ -  socksreq[len++] = (unsigned char)(remote_port & 0xff);        /* PORT LSB */ +  CONNECT_REQ_SEND: +  case CONNECT_REQ_SEND: +    /* PORT MSB */ +    socksreq[len++] = (unsigned char)((remote_port >> 8) & 0xff); +    /* PORT LSB */ +    socksreq[len++] = (unsigned char)(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."); -  } -  else +    if(conn->socks5_gssapi_enctype) { +      failf(data, "SOCKS5 GSS-API protection not yet implemented."); +      return CURLE_COULDNT_CONNECT; +    }  #endif -    code = Curl_write_plain(conn, sock, (char *)socksreq, len, &written); - -  if(code || (len != written)) { -    failf(data, "Failed to send SOCKS5 connect request."); -    return CURLE_COULDNT_CONNECT; -  } - -  len = 10; /* minimum packet size is 10 */ - +    sx->outp = socksreq; +    sx->outstanding = len; +    sxstate(conn, CONNECT_REQ_SENDING); +    /* FALLTHROUGH */ +  case CONNECT_REQ_SENDING: +    result = Curl_write_plain(conn, sockfd, (char *)sx->outp, +                              sx->outstanding, &written); +    if(result && (CURLE_AGAIN != result)) { +      failf(data, "Failed to send SOCKS5 connect request."); +      return CURLE_COULDNT_CONNECT; +    } +    if(sx->outstanding != written) { +      /* remain in state */ +      sx->outstanding -= written; +      sx->outp += written; +      return CURLE_OK; +    }  #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) -  if(conn->socks5_gssapi_enctype) { -    failf(data, "SOCKS5 GSS-API protection not yet implemented."); -  } -  else +    if(conn->socks5_gssapi_enctype) { +      failf(data, "SOCKS5 GSS-API protection not yet implemented."); +      return CURLE_COULDNT_CONNECT; +    }  #endif -    result = Curl_blockread_all(conn, sock, (char *)socksreq, -                                len, &actualread); - -  if(result || (len != actualread)) { -    failf(data, "Failed to receive SOCKS5 connect request ack."); -    return CURLE_COULDNT_CONNECT; -  } - -  if(socksreq[0] != 5) { /* version */ -    failf(data, -          "SOCKS5 reply has wrong version, version should be 5."); -    return CURLE_COULDNT_CONNECT; -  } - -  /* 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     | -    +----+-----+-------+------+----------+----------+ +    sx->outstanding = 10; /* minimum packet size is 10 */ +    sx->outp = socksreq; +    sxstate(conn, CONNECT_REQ_READ); +    /* FALLTHROUGH */ +  case CONNECT_REQ_READ: +    result = Curl_read_plain(sockfd, (char *)sx->outp, +                             sx->outstanding, &actualread); +    if(result && (CURLE_AGAIN != result)) { +      failf(data, "Failed to receive SOCKS5 connect request ack."); +      return CURLE_COULDNT_CONNECT; +    } +    else if(actualread != sx->outstanding) { +      /* remain in state */ +      sx->outstanding -= actualread; +      sx->outp += actualread; +      return CURLE_OK; +    } -     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 -     */ +    if(socksreq[0] != 5) { /* version */ +      failf(data, +            "SOCKS5 reply has wrong version, version should be 5."); +      return CURLE_COULDNT_CONNECT; +    } +    else if(socksreq[1] != 0) { /* Anything besides 0 is an error */ +      failf(data, "Can't complete SOCKS5 connection to %s. (%d)", +            hostname, (unsigned char)socksreq[1]); +      return CURLE_COULDNT_CONNECT; +    } -  /* 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; -  } +    /* 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; +    } -  /* At this point we already read first 10 bytes */ +    /* 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 */ +    if(!conn->socks5_gssapi_enctype) { +      /* decrypt_gssapi_blockread already read the whole packet */  #endif -    if(len > 10) { -      result = Curl_blockread_all(conn, sock, (char *)&socksreq[10], -                                  len - 10, &actualread); -      if(result || ((len - 10) != actualread)) { -        failf(data, "Failed to receive SOCKS5 connect request ack."); -        return CURLE_COULDNT_CONNECT; +      if(len > 10) { +        sx->outstanding = len - 10; /* get the rest */ +        sx->outp = &socksreq[10]; +        sxstate(conn, CONNECT_REQ_READ_MORE); +      } +      else { +        sxstate(conn, CONNECT_DONE); +        break;        } -    }  #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) -  } +    }  #endif - -  if(socksreq[1] != 0) { /* Anything besides 0 is an error */ -    failf(data, "Can't complete SOCKS5 connection to %s. (%d)", -          dest, (unsigned char)socksreq[1]); -    return CURLE_COULDNT_CONNECT; +    /* FALLTHROUGH */ +  case CONNECT_REQ_READ_MORE: +    result = Curl_read_plain(sockfd, (char *)sx->outp, +                             sx->outstanding, &actualread); +    if(result && (CURLE_AGAIN != result)) { +      failf(data, "Failed to receive SOCKS5 connect request ack."); +      return CURLE_COULDNT_CONNECT; +    } +    if(actualread != sx->outstanding) { +      /* remain in state */ +      sx->outstanding -= actualread; +      sx->outp += actualread; +      return CURLE_OK; +    } +    sxstate(conn, CONNECT_DONE);    }    infof(data, "SOCKS5 request granted.\n"); -  (void)curlx_nonblock(sock, TRUE); +  *done = TRUE;    return CURLE_OK; /* Proxy was successful! */  } diff --git a/lib/socks.h b/lib/socks.h index 3b319a6ef..64a756337 100644 --- a/lib/socks.h +++ b/lib/socks.h @@ -7,7 +7,7 @@   *                            | (__| |_| |  _ <| |___   *                             \___|\___/|_| \_\_____|   * - * Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) 1998 - 2020, Daniel Stenberg, <daniel@haxx.se>, et al.   *   * This software is licensed as described in the file COPYING, which   * you should have received as part of this distribution. The terms @@ -27,13 +27,13 @@  #ifdef CURL_DISABLE_PROXY  #define Curl_SOCKS4(a,b,c,d,e) CURLE_NOT_BUILT_IN  #define Curl_SOCKS5(a,b,c,d,e,f) CURLE_NOT_BUILT_IN +#define Curl_SOCKS_getsock(x,y,z) 0  #else  /*   * Helper read-from-socket functions. Does the same as Curl_read() but it   * blocks until all bytes amount of buffersize will be read. No more, no less.   * - * This is STUPID BLOCKING behaviour which we frown upon, but right now this - * is what we have... + * This is STUPID BLOCKING behavior   */  int Curl_blockread_all(struct connectdata *conn,                         curl_socket_t sockfd, @@ -41,6 +41,9 @@ int Curl_blockread_all(struct connectdata *conn,                         ssize_t buffersize,                         ssize_t *n); +int Curl_SOCKS_getsock(struct connectdata *conn, +                       curl_socket_t *sock, +                       int sockindex);  /*   * This function logs in to a SOCKS4(a) proxy and sends the specifics to the   * final destination server. @@ -49,7 +52,8 @@ CURLcode Curl_SOCKS4(const char *proxy_name,                       const char *hostname,                       int remote_port,                       int sockindex, -                     struct connectdata *conn); +                     struct connectdata *conn, +                     bool *done);  /*   * This function logs in to a SOCKS5 proxy and sends the specifics to the @@ -60,7 +64,8 @@ CURLcode Curl_SOCKS5(const char *proxy_name,                       const char *hostname,                       int remote_port,                       int sockindex, -                     struct connectdata *conn); +                     struct connectdata *conn, +                     bool *done);  #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)  /* diff --git a/lib/socks_gssapi.c b/lib/socks_gssapi.c index 65294bbeb..97ee7183e 100644 --- a/lib/socks_gssapi.c +++ b/lib/socks_gssapi.c @@ -5,8 +5,8 @@   *                            | (__| |_| |  _ <| |___   *                             \___|\___/|_| \_\_____|   * + * Copyright (C) 2012 - 2020, Daniel Stenberg, <daniel@haxx.se>, et al.   * Copyright (C) 2009, Markus Moeller, <markus_moeller@compuserve.com> - * Copyright (C) 2012 - 2018, Daniel Stenberg, <daniel@haxx.se>, et al.   *   * This software is licensed as described in the file COPYING, which   * you should have received as part of this distribution. The terms @@ -167,6 +167,8 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex,      return CURLE_COULDNT_CONNECT;    } +  (void)curlx_nonblock(sock, FALSE); +    /* As long as we need to keep sending some context info, and there's no  */    /* errors, keep sending it...                                            */    for(;;) { @@ -513,6 +515,8 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex,      gss_release_buffer(&gss_status, &gss_recv_token);    } +  (void)curlx_nonblock(sock, TRUE); +    infof(data, "SOCKS5 access with%s protection granted.\n",          (socksreq[0] == 0)?"out GSS-API data":          ((socksreq[0] == 1)?" GSS-API integrity":" GSS-API confidentiality")); diff --git a/lib/socks_sspi.c b/lib/socks_sspi.c index 57027ef68..d5be64a3c 100644 --- a/lib/socks_sspi.c +++ b/lib/socks_sspi.c @@ -5,7 +5,7 @@   *                            | (__| |_| |  _ <| |___   *                             \___|\___/|_| \_\_____|   * - * Copyright (C) 2012 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) 2012 - 2020, Daniel Stenberg, <daniel@haxx.se>, et al.   * Copyright (C) 2009, 2011, Markus Moeller, <markus_moeller@compuserve.com>   *   * This software is licensed as described in the file COPYING, which @@ -153,6 +153,8 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex,      return CURLE_COULDNT_CONNECT;    } +  (void)curlx_nonblock(sock, FALSE); +    /* As long as we need to keep sending some context info, and there's no  */    /* errors, keep sending it...                                            */    for(;;) { @@ -587,6 +589,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex,      memcpy(socksreq, sspi_w_token[0].pvBuffer, sspi_w_token[0].cbBuffer);      s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer);    } +  (void)curlx_nonblock(sock, TRUE);    infof(data, "SOCKS5 access with%s protection granted.\n",          (socksreq[0] == 0)?"out GSS-API data": diff --git a/lib/urldata.h b/lib/urldata.h index e1c3e181b..6401f49f2 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -476,7 +476,6 @@ struct ConnectBits {    BIT(tcp_fastopen); /* use TCP Fast Open */    BIT(tls_enable_npn);  /* TLS NPN extension? */    BIT(tls_enable_alpn); /* TLS ALPN extension? */ -  BIT(socksproxy_connecting); /* connecting through a socks proxy */    BIT(connect_only);  }; @@ -817,6 +816,41 @@ struct http_connect_state {  struct ldapconninfo; +/* for the (SOCKS) connect state machine */ +enum connect_t { +  CONNECT_INIT, +  CONNECT_SOCKS_INIT, /* 1 */ +  CONNECT_SOCKS_SEND, /* 2 waiting to send more first data */ +  CONNECT_SOCKS_READ_INIT, /* 3 set up read */ +  CONNECT_SOCKS_READ, /* 4 read server response */ +  CONNECT_GSSAPI_INIT, /* 5 */ +  CONNECT_AUTH_INIT, /* 6 setup outgoing auth buffer */ +  CONNECT_AUTH_SEND, /* 7 send auth */ +  CONNECT_AUTH_READ, /* 8 read auth response */ +  CONNECT_REQ_INIT,  /* 9 init SOCKS "request" */ +  CONNECT_RESOLVING, /* 10 */ +  CONNECT_RESOLVED,  /* 11 */ +  CONNECT_RESOLVE_REMOTE, /* 12 */ +  CONNECT_REQ_SEND,  /* 13 */ +  CONNECT_REQ_SENDING, /* 14 */ +  CONNECT_REQ_READ,  /* 15 */ +  CONNECT_REQ_READ_MORE, /* 16 */ +  CONNECT_DONE /* 17 connected fine to the remote or the SOCKS proxy */ +}; + +#define SOCKS_STATE(x) (((x) >= CONNECT_SOCKS_INIT) &&  \ +                        ((x) < CONNECT_DONE)) +#define SOCKS_REQUEST_BUFSIZE 600  /* room for large user/pw (255 max each) */ + +struct connstate { +  enum connect_t state; +  unsigned char socksreq[SOCKS_REQUEST_BUFSIZE]; + +  /* CONNECT_SOCKS_SEND */ +  ssize_t outstanding;  /* send this many bytes more */ +  unsigned char *outp; /* send from this pointer */ +}; +  /*   * The connectdata struct contains all fields and variables that should be   * unique for an entire connection. @@ -826,7 +860,7 @@ struct connectdata {       caution that this might very well vary between different times this       connection is used! */    struct Curl_easy *data; - +  struct connstate cnnct;    struct curl_llist_element bundle_node; /* conncache */    /* chunk is for HTTP chunked encoding, but is in the general connectdata | 
