]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
socks: rewwork, cleaning up socks state handling
authorStefan Eissing <stefan@eissing.org>
Tue, 26 Aug 2025 13:54:32 +0000 (15:54 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Thu, 25 Sep 2025 12:03:10 +0000 (14:03 +0200)
Restructured the code in the following ways:

* add terminal states SUCCESS and FAILED
* split SOCK4 and SOCK5 states to be more clear
* use `bufq` for send/recv of SOCK messages
* reduce SOCKS4 states, more speaking names
* for most states, move code into static function
* reduce SOCKS5 states, more speaking names
* add helpers for traversing to FAILED state
* add helper to flush bufq
* add hepler to read minimum amount into bufq

Closes #18401

lib/bufq.h
lib/socks.c
tests/libtest/lib564.c

index 7cd1826a955cc6a52493553813a15e0ddefc2b97..ad8e6435fae1e2e34cf220f9a38a8f0c50272a56 100644 (file)
@@ -225,7 +225,7 @@ typedef CURLcode Curl_bufq_reader(void *reader_ctx,
                                   size_t *pnread);
 
 /**
- * Read date and append it to the end of the buffer queue until the
+ * Read bytes and append them to the end of the buffer queue until the
  * reader returns blocking or the queue is full. A reader returns
  * CURLE_AGAIN to indicate blocking.
  * Returns the total amount of buf read (may be 0) in `pnread` on success.
index 4cb8619d7bd5ef555c40c7950ea6ba45ad76ec4d..6927e32c3ecbe98ddd979b4114e2155ede59451d 100644 (file)
@@ -34,6 +34,7 @@
 #endif
 
 #include "urldata.h"
+#include "bufq.h"
 #include "sendf.h"
 #include "select.h"
 #include "cfilters.h"
 #include "curl_memory.h"
 #include "memdebug.h"
 
+#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
+#define DEBUG_AND_VERBOSE
+#endif
+
 /* 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 */
+enum socks_state_t {
+  SOCKS_ST_INIT,
+  /* SOCKS Version 4 states */
+  SOCKS4_ST_START,
+  SOCKS4_ST_RESOLVING,
+  SOCKS4_ST_SEND,
+  SOCKS4_ST_RECV,
+  /* SOCKS Version 5 states */
+  SOCKS5_ST_START,
+  SOCKS5_ST_REQ0_SEND,
+  SOCKS5_ST_RESP0_RECV, /* set up read */
+  SOCKS5_ST_GSSAPI_INIT,
+  SOCKS5_ST_AUTH_INIT, /* setup outgoing auth buffer */
+  SOCKS5_ST_AUTH_SEND, /* send auth */
+  SOCKS5_ST_AUTH_RECV, /* read auth response */
+  SOCKS5_ST_REQ1_INIT,  /* init SOCKS "request" */
+  SOCKS5_ST_RESOLVING,
+  SOCKS5_ST_REQ1_SEND,
+  SOCKS5_ST_RESP1_RECV,
+  /* Terminal states, all SOCKS versions */
+  SOCKS_ST_SUCCESS,
+  SOCKS_ST_FAILED
 };
 
-#define CURL_SOCKS_BUF_SIZE 600
-
-/* make sure we configure it not too low */
-#if CURL_SOCKS_BUF_SIZE < 600
-#error CURL_SOCKS_BUF_SIZE must be at least 600
+#ifdef DEBUG_AND_VERBOSE
+static const char * const cf_socks_statename[] = {
+  "SOCKS_INIT",
+  "SOCKS4_START",
+  "SOCKS4_RESOLVING",
+  "SOCKS4_SEND",
+  "SOCKS4_RECV",
+  "SOCKS5_START",
+  "SOCKS5_REQ0_SEND",
+  "SOCKS5_RESP0_RECV",
+  "SOCKS5_GSSAPI_INIT",
+  "SOCKS5_AUTH_INIT",
+  "SOCKS5_AUTH_SEND",
+  "SOCKS5_AUTH_RECV",
+  "SOCKS5_REQ1_INIT",
+  "SOCKS5_RESOLVING",
+  "SOCKS5_REQ1_SEND",
+  "SOCKS5_RESP1_RECV",
+  "SOCKS_SUCCESS",
+  "SOCKS_FAILED"
+};
 #endif
 
+#define SOCKS_CHUNK_SIZE    1024
+#define SOCKS_CHUNKS        1
 
-struct socks_state {
-  enum connect_t state;
-  size_t outstanding;  /* send this many bytes more */
-  unsigned char buffer[CURL_SOCKS_BUF_SIZE];
-  unsigned char *outp; /* send from this pointer */
 
+struct socks_state {
+  enum socks_state_t state;
+  struct bufq iobuf;
   const char *hostname;
   int remote_port;
   const char *proxy_user;
   const char *proxy_password;
+  CURLproxycode presult;
+  unsigned char version;
+  BIT(resolve_local);
+  BIT(start_resolving);
+  BIT(socks4a);
 };
 
 #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
@@ -149,37 +178,13 @@ CURLcode Curl_blockread_all(struct Curl_cfilter *cf,
 static void socksstate(struct socks_state *sx,
                        struct Curl_cfilter *cf,
                        struct Curl_easy *data,
-                       enum connect_t state
+                       enum socks_state_t state
 #ifdef DEBUG_AND_VERBOSE
                        , int lineno
 #endif
 )
 {
-  enum connect_t oldstate = sx->state;
-#ifdef DEBUG_AND_VERBOSE
-  /* synced with the state list in urldata.h */
-  static const char * const socks_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
-
+  enum socks_state_t oldstate = sx->state;
   (void)cf;
   (void)data;
   if(oldstate == state)
@@ -190,274 +195,209 @@ static void socksstate(struct socks_state *sx,
 
 #ifdef DEBUG_AND_VERBOSE
   CURL_TRC_CF(data, cf, "[%s] -> [%s] (line %d)",
-              socks_statename[oldstate], socks_statename[sx->state], lineno);
+              cf_socks_statename[oldstate],
+              cf_socks_statename[sx->state], lineno);
 #endif
 }
 
-static CURLproxycode socks_state_send(struct Curl_cfilter *cf,
-                                      struct socks_state *sx,
-                                      struct Curl_easy *data,
-                                      CURLproxycode failcode,
-                                      const char *description)
+static CURLproxycode socks_failed(struct socks_state *sx,
+                                  struct Curl_cfilter *cf,
+                                  struct Curl_easy *data,
+                                  CURLproxycode presult)
+{
+  sxstate(sx, cf, data, SOCKS_ST_FAILED);
+  sx->presult = presult;
+  return presult;
+}
+
+static CURLproxycode socks_flush(struct socks_state *sx,
+                                  struct Curl_cfilter *cf,
+                                  struct Curl_easy *data,
+                                  bool *done)
 {
-  size_t nwritten;
   CURLcode result;
+  size_t nwritten;
 
-  result = Curl_conn_cf_send(cf->next, data, (char *)sx->outp,
-                             sx->outstanding, FALSE, &nwritten);
-  if(result) {
-    if(CURLE_AGAIN == result)
+  *done = FALSE;
+  while(!Curl_bufq_is_empty(&sx->iobuf)) {
+    result = Curl_cf_send_bufq(cf->next, data, &sx->iobuf, NULL, 0,
+                               &nwritten);
+    if(result == CURLE_AGAIN)
       return CURLPX_OK;
-
-    failf(data, "Failed to send %s: %s", description,
-          curl_easy_strerror(result));
-    return failcode;
-  }
-  else if(!nwritten) {
-    /* connection closed */
-    failf(data, "connection to proxy closed");
-    return CURLPX_CLOSED;
+    else if(result) {
+      failf(data, "Failed to send SOCKS request: %s",
+            curl_easy_strerror(result));
+      return socks_failed(sx, cf, data, CURLPX_SEND_CONNECT);
+    }
   }
-
-  DEBUGASSERT(sx->outstanding >= nwritten);
-  /* not done, remain in state */
-  sx->outstanding -= nwritten;
-  sx->outp += nwritten;
+  *done = TRUE;
   return CURLPX_OK;
 }
 
-static CURLproxycode socks_state_recv(struct Curl_cfilter *cf,
-                                      struct socks_state *sx,
-                                      struct Curl_easy *data,
-                                      CURLproxycode failcode,
-                                      const char *description)
+static CURLproxycode socks_recv(struct socks_state *sx,
+                                struct Curl_cfilter *cf,
+                                struct Curl_easy *data,
+                                size_t min_bytes,
+                                bool *done)
 {
-  size_t nread;
   CURLcode result;
+  size_t nread;
 
-  result = Curl_conn_cf_recv(cf->next, data, (char *)sx->outp,
-                            sx->outstanding, &nread);
-  if(result) {
-    if(CURLE_AGAIN == result)
+  *done = FALSE;
+  while(Curl_bufq_len(&sx->iobuf) < min_bytes) {
+    result = Curl_cf_recv_bufq(cf->next, data, &sx->iobuf,
+                               min_bytes - Curl_bufq_len(&sx->iobuf),
+                               &nread);
+    if(result == CURLE_AGAIN)
       return CURLPX_OK;
-
-    failf(data, "SOCKS: Failed receiving %s: %s", description,
-          curl_easy_strerror(result));
-    return failcode;
-  }
-  else if(!nread) {
-    /* connection closed */
-    failf(data, "connection to proxy closed");
-    return CURLPX_CLOSED;
+    else if(result) {
+      failf(data, "Failed to receive SOCKS response: %s",
+            curl_easy_strerror(result));
+      return CURLPX_RECV_CONNECT;
+    }
+    else if(!nread) /* EOF */
+      break;
   }
-  /* remain in reading state */
-  DEBUGASSERT(sx->outstanding >= nread);
-  sx->outstanding -= nread;
-  sx->outp += nread;
+  *done = TRUE;
   return CURLPX_OK;
 }
 
-/*
-* This function logs in to a SOCKS4 proxy and sends the specifics to the final
-* destination server.
-*
-* Reference :
-*   https://www.openssh.com/txt/socks4.protocol
-*
-* Note :
-*   Set protocol4a=true for  "SOCKS 4A (Simple Extension to SOCKS 4 Protocol)"
-*   Nonsupport "Identification Protocol (RFC1413)"
-*/
-static CURLproxycode do_SOCKS4(struct Curl_cfilter *cf,
-                               struct socks_state *sx,
-                               struct Curl_easy *data)
+static CURLproxycode socks4_req_add_hd(struct socks_state *sx,
+                                       struct Curl_easy *data)
 {
-  struct connectdata *conn = cf->conn;
-  const bool protocol4a =
-    (conn->socks_proxy.proxytype == CURLPROXY_SOCKS4A);
-  unsigned char *socksreq = sx->buffer;
+  unsigned char buf[4];
+  size_t nwritten;
   CURLcode result;
-  CURLproxycode presult;
-  struct Curl_dns_entry *dns = NULL;
-
-  switch(sx->state) {
-  case CONNECT_SOCKS_INIT:
-    /* SOCKS4 can only do IPv4, insist! */
-    conn->ip_version = CURL_IPRESOLVE_V4;
-    CURL_TRC_CF(data, cf, "SOCKS4%s communication to%s %s:%d",
-                protocol4a ? "a" : "",
-                conn->bits.httpproxy ? " HTTP proxy" : "",
-                sx->hostname, sx->remote_port);
 
-    /*
-     * Compose socks4 request
-     *
-     * Request format
-     *
-     *     +----+----+----+----+----+----+----+----+----+----+....+----+
-     *     | VN | CD | DSTPORT |      DSTIP        | USERID       |NULL|
-     *     +----+----+----+----+----+----+----+----+----+----+....+----+
-     * # of bytes:  1    1      2              4           variable       1
-     */
+  (void)data;
+  buf[0] = 4; /* version (SOCKS4) */
+  buf[1] = 1; /* connect */
+  buf[2] = (unsigned char)((sx->remote_port >> 8) & 0xffu); /* MSB */
+  buf[3] = (unsigned char)(sx->remote_port & 0xffu);        /* LSB */
+
+  result = Curl_bufq_write(&sx->iobuf, buf, 4, &nwritten);
+  if(result || (nwritten != 4))
+    return CURLPX_SEND_REQUEST;
+  return CURLPX_OK;
+}
 
-    socksreq[0] = 4; /* version (SOCKS4) */
-    socksreq[1] = 1; /* connect */
-    socksreq[2] = (unsigned char)((sx->remote_port >> 8) & 0xff); /* MSB */
-    socksreq[3] = (unsigned char)(sx->remote_port & 0xff);        /* LSB */
+static CURLproxycode socks4_req_add_user(struct socks_state *sx,
+                                         struct Curl_easy *data)
+{
+  CURLcode result;
+  size_t nwritten;
 
-    /* DNS resolve only for SOCKS4, not SOCKS4a */
-    if(!protocol4a) {
-      result = Curl_resolv(data, sx->hostname, sx->remote_port,
-                           cf->conn->ip_version, TRUE, &dns);
-
-      if(result == CURLE_AGAIN) {
-        sxstate(sx, cf, data, CONNECT_RESOLVING);
-        CURL_TRC_CF(data, cf, "SOCKS4 non-blocking resolve of %s",
-                    sx->hostname);
-        return CURLPX_OK;
-      }
-      else if(result)
-        return CURLPX_RESOLVE_HOST;
-      sxstate(sx, cf, data, CONNECT_RESOLVED);
-      goto CONNECT_RESOLVED;
+  if(sx->proxy_user) {
+    size_t plen = strlen(sx->proxy_user);
+    if(plen > 255) {
+      /* there is no real size limit to this field in the protocol, but
+         SOCKS5 limits the proxy user field to 255 bytes and it seems likely
+         that a longer field is either a mistake or malicious input */
+      failf(data, "Too long SOCKS proxy username");
+      return CURLPX_LONG_USER;
     }
+    /* add proxy name WITH trailing zero */
+    result = Curl_bufq_cwrite(&sx->iobuf, sx->proxy_user, plen + 1,
+                              &nwritten);
+    if(result || (nwritten != (plen + 1)))
+      return CURLPX_SEND_REQUEST;
+  }
+  else {
+    /* empty user name */
+    unsigned char b = 0;
+    result = Curl_bufq_write(&sx->iobuf, &b, 1, &nwritten);
+    if(result || (nwritten != 1))
+      return CURLPX_SEND_REQUEST;
+  }
+  return CURLPX_OK;
+}
+
+static CURLproxycode socks4_resolving(struct socks_state *sx,
+                                      struct Curl_cfilter *cf,
+                                      struct Curl_easy *data,
+                                      bool *done)
+{
+  struct Curl_dns_entry *dns = NULL;
+  struct Curl_addrinfo *hp = NULL;
+  CURLcode result;
+  size_t nwritten;
 
-    /* socks4a does not resolve anything locally */
-    sxstate(sx, cf, data, CONNECT_REQ_INIT);
-    goto CONNECT_REQ_INIT;
+  *done = FALSE;
+  if(sx->start_resolving) {
+    /* need to resolve hostname to add destination address */
+    sx->start_resolving = FALSE;
 
-  case CONNECT_RESOLVING:
+    result = Curl_resolv(data, sx->hostname, sx->remote_port,
+                         cf->conn->ip_version, TRUE, &dns);
+    if(result == CURLE_AGAIN) {
+      CURL_TRC_CF(data, cf, "SOCKS4 non-blocking resolve of %s",
+                  sx->hostname);
+      return CURLPX_OK;
+    }
+    else if(result)
+      return CURLPX_RESOLVE_HOST;
+  }
+  else {
     /* check if we have the name resolved by now */
     result = Curl_resolv_check(data, &dns);
-    if(!dns) {
-      if(result)
-        return CURLPX_RESOLVE_HOST;
+    if(!result && !dns)
       return CURLPX_OK;
-    }
-    FALLTHROUGH();
-  case CONNECT_RESOLVED:
-CONNECT_RESOLVED:
-  {
-    struct Curl_addrinfo *hp = NULL;
-    /*
-     * 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) {
-      hp = dns->addr;
-
-      /* scan for the first IPv4 address */
-      while(hp && (hp->ai_family != AF_INET))
-        hp = hp->ai_next;
-
-      if(hp) {
-        struct sockaddr_in *saddr_in;
-        char buf[64];
-        Curl_printable_address(hp, buf, sizeof(buf));
-
-        saddr_in = (struct sockaddr_in *)(void *)hp->ai_addr;
-        socksreq[4] = ((unsigned char *)&saddr_in->sin_addr.s_addr)[0];
-        socksreq[5] = ((unsigned char *)&saddr_in->sin_addr.s_addr)[1];
-        socksreq[6] = ((unsigned char *)&saddr_in->sin_addr.s_addr)[2];
-        socksreq[7] = ((unsigned char *)&saddr_in->sin_addr.s_addr)[3];
-
-        CURL_TRC_CF(data, cf, "SOCKS4 connect to IPv4 %s (locally resolved)",
-                    buf);
-        Curl_resolv_unlink(data, &dns); /* not used anymore from now on */
-      }
-      else
-        failf(data, "SOCKS4 connection to %s not supported", sx->hostname);
-    }
-    else
-      failf(data, "Failed to resolve \"%s\" for SOCKS4 connect.",
-            sx->hostname);
+  }
 
-    if(!hp)
-      return CURLPX_RESOLVE_HOST;
+  if(result || !dns) {
+    failf(data, "Failed to resolve \"%s\" for SOCKS4 connect.",
+          sx->hostname);
+    return CURLPX_RESOLVE_HOST;
   }
-    FALLTHROUGH();
-  case CONNECT_REQ_INIT:
-CONNECT_REQ_INIT:
-    /*
-     * This is currently not supporting "Identification Protocol (RFC1413)".
-     */
-    socksreq[8] = 0; /* ensure empty userid is null-terminated */
-    if(sx->proxy_user) {
-      size_t plen = strlen(sx->proxy_user);
-      if(plen > 255) {
-        /* there is no real size limit to this field in the protocol, but
-           SOCKS5 limits the proxy user field to 255 bytes and it seems likely
-           that a longer field is either a mistake or malicious input */
-        failf(data, "Too long SOCKS proxy username");
-        return CURLPX_LONG_USER;
-      }
-      /* copy the proxy name WITH trailing zero */
-      memcpy(socksreq + 8, sx->proxy_user, plen + 1);
-    }
 
-    /*
-     * Make connection
-     */
-    {
-      size_t packetsize = 9 +
-        strlen((char *)socksreq + 8); /* size including NUL */
-
-      /* If SOCKS4a, set special invalid IP address 0.0.0.x */
-      if(protocol4a) {
-        size_t hostnamelen = 0;
-        socksreq[4] = 0;
-        socksreq[5] = 0;
-        socksreq[6] = 0;
-        socksreq[7] = 1;
-        /* append hostname */
-        hostnamelen = strlen(sx->hostname) + 1; /* length including NUL */
-        if((hostnamelen <= 255) &&
-           (packetsize + hostnamelen < sizeof(sx->buffer)))
-          strcpy((char *)socksreq + packetsize, sx->hostname);
-        else {
-          failf(data, "SOCKS4: too long hostname");
-          return CURLPX_LONG_HOSTNAME;
-        }
-        packetsize += hostnamelen;
-      }
-      sx->outp = socksreq;
-      DEBUGASSERT(packetsize <= sizeof(sx->buffer));
-      sx->outstanding = packetsize;
-      sxstate(sx, cf, data, CONNECT_REQ_SENDING);
-    }
-    FALLTHROUGH();
-  case CONNECT_REQ_SENDING:
-    /* Send request */
-    presult = socks_state_send(cf, sx, data, CURLPX_SEND_CONNECT,
-                               "SOCKS4 connect request");
-    if(CURLPX_OK != presult)
-      return presult;
-    else if(sx->outstanding) {
-      /* remain in sending state */
-      return CURLPX_OK;
-    }
-    /* done sending! */
-    sx->outstanding = 8; /* receive data size */
-    sx->outp = socksreq;
-    sxstate(sx, cf, data, CONNECT_SOCKS_READ);
+  /*
+   * We cannot use 'hostent' as a struct that Curl_resolv() returns. It
+   * returns a Curl_addrinfo pointer that may not always look the same.
+   */
+  /* scan for the first IPv4 address */
+  hp = dns->addr;
+  while(hp && (hp->ai_family != AF_INET))
+    hp = hp->ai_next;
 
-    FALLTHROUGH();
-  case CONNECT_SOCKS_READ:
-    /* Receive response */
-    presult = socks_state_recv(cf, sx, data, CURLPX_RECV_CONNECT,
-                               "connect request ack");
-    if(CURLPX_OK != presult)
-      return presult;
-    else if(sx->outstanding) {
-      /* remain in reading state */
-      return CURLPX_OK;
-    }
-    sxstate(sx, cf, data, CONNECT_DONE);
-    break;
-  default: /* lots of unused states in SOCKS4 */
-    break;
+  if(hp) {
+    struct sockaddr_in *saddr_in;
+    char ipbuf[64];
+
+    Curl_printable_address(hp, ipbuf, sizeof(ipbuf));
+    CURL_TRC_CF(data, cf, "SOCKS4 connect to IPv4 %s (locally resolved)",
+                ipbuf);
+
+    saddr_in = (struct sockaddr_in *)(void *)hp->ai_addr;
+    result = Curl_bufq_write(&sx->iobuf,
+                             (unsigned char *)&saddr_in->sin_addr.s_addr, 4,
+                             &nwritten);
+
+    Curl_resolv_unlink(data, &dns); /* not used anymore from now on */
+    if(result || (nwritten != 4))
+      return CURLPX_SEND_REQUEST;
+  }
+  else {
+    failf(data, "SOCKS4 connection to %s not supported", sx->hostname);
+    return CURLPX_RESOLVE_HOST;
+  }
+
+  *done = TRUE;
+  return CURLPX_OK;
+}
+
+static CURLproxycode socks4_check_resp(struct socks_state *sx,
+                                       struct Curl_cfilter *cf,
+                                       struct Curl_easy *data)
+{
+  const unsigned char *resp;
+  size_t rlen;
+
+  if(!Curl_bufq_peek(&sx->iobuf, &resp, &rlen) || rlen < 8) {
+    failf(data, "SOCKS4 reply is incomplete.");
+    return CURLPX_RECV_CONNECT;
   }
 
+  DEBUGASSERT(rlen == 8);
   /*
    * Response format
    *
@@ -478,72 +418,194 @@ CONNECT_REQ_INIT:
    */
 
   /* wrong version ? */
-  if(socksreq[0]) {
-    failf(data,
-          "SOCKS4 reply has wrong version, version should be 0.");
+  if(resp[0]) {
+    failf(data, "SOCKS4 reply has wrong version, version should be 0.");
     return CURLPX_BAD_VERSION;
   }
 
   /* Result */
-  switch(socksreq[1]) {
+  switch(resp[1]) {
   case 90:
-    CURL_TRC_CF(data, cf, "SOCKS4%s request granted.", protocol4a ? "a" : "");
-    break;
+    CURL_TRC_CF(data, cf, "SOCKS4%s request granted.", sx->socks4a ? "a" : "");
+    Curl_bufq_reset(&sx->iobuf);
+    return CURLPX_OK;
   case 91:
     failf(data,
-          "[SOCKS] cannot complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)"
+          "[SOCKS] cannot complete SOCKS4 connection to %u.%u.%u.%u:%u. (%u)"
           ", request rejected or failed.",
-          socksreq[4], socksreq[5], socksreq[6], socksreq[7],
-          (((unsigned char)socksreq[2] << 8) | (unsigned char)socksreq[3]),
-          (unsigned char)socksreq[1]);
+          resp[4], resp[5], resp[6], resp[7],
+          ((resp[2] << 8) | resp[3]), resp[1]);
     return CURLPX_REQUEST_FAILED;
   case 92:
     failf(data,
-          "[SOCKS] cannot complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)"
+          "[SOCKS] cannot complete SOCKS4 connection to %u.%u.%u.%u:%u. (%u)"
           ", request rejected because SOCKS server cannot connect to "
           "identd on the client.",
-          socksreq[4], socksreq[5], socksreq[6], socksreq[7],
-          (((unsigned char)socksreq[2] << 8) | (unsigned char)socksreq[3]),
-          (unsigned char)socksreq[1]);
+          resp[4], resp[5], resp[6], resp[7],
+          ((resp[2] << 8) | resp[3]), resp[1]);
     return CURLPX_IDENTD;
   case 93:
     failf(data,
-          "[SOCKS] cannot complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)"
+          "[SOCKS] cannot complete SOCKS4 connection to %u.%u.%u.%u:%u. (%u)"
           ", request rejected because the client program and identd "
           "report different user-ids.",
-          socksreq[4], socksreq[5], socksreq[6], socksreq[7],
-          (((unsigned char)socksreq[2] << 8) | (unsigned char)socksreq[3]),
-          (unsigned char)socksreq[1]);
+          resp[4], resp[5], resp[6], resp[7],
+          ((resp[2] << 8) | resp[3]), resp[1]);
     return CURLPX_IDENTD_DIFFER;
   default:
     failf(data,
-          "[SOCKS] cannot complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)"
+          "[SOCKS] cannot complete SOCKS4 connection to %u.%u.%u.%u:%u. (%u)"
           ", Unknown.",
-          socksreq[4], socksreq[5], socksreq[6], socksreq[7],
-          (((unsigned char)socksreq[2] << 8) | (unsigned char)socksreq[3]),
-          (unsigned char)socksreq[1]);
+          resp[4], resp[5], resp[6], resp[7],
+          ((resp[2] << 8) | resp[3]), resp[1]);
     return CURLPX_UNKNOWN_FAIL;
   }
-
-  return CURLPX_OK; /* Proxy was successful! */
 }
 
-static CURLproxycode socks5_init(struct Curl_cfilter *cf,
-                                 struct socks_state *sx,
-                                 struct Curl_easy *data,
-                                 const bool socks5_resolve_local,
-                                 const size_t hostname_len)
+/*
+* This function logs in to a SOCKS4 proxy and sends the specifics to the final
+* destination server.
+*
+* Reference :
+*   https://www.openssh.com/txt/socks4.protocol
+*
+* Note :
+*   Set protocol4a=true for  "SOCKS 4A (Simple Extension to SOCKS 4 Protocol)"
+*   Nonsupport "Identification Protocol (RFC1413)"
+*/
+static CURLproxycode socks4_connect(struct Curl_cfilter *cf,
+                                    struct socks_state *sx,
+                                    struct Curl_easy *data)
 {
-  struct connectdata *conn = cf->conn;
-  const unsigned char auth = data->set.socks5auth;
-  unsigned char *socksreq = sx->buffer;
+  size_t nwritten;
+  CURLproxycode presult;
+  CURLcode result;
+  bool done;
+
+process_state:
+  switch(sx->state) {
+  case SOCKS_ST_INIT:
+    sx->version = 4;
+    sxstate(sx, cf, data, SOCKS4_ST_START);
+    FALLTHROUGH();
 
-  if(conn->bits.httpproxy)
-    CURL_TRC_CF(data, cf, "SOCKS5: connecting to HTTP proxy %s port %d",
+  case SOCKS4_ST_START:
+    Curl_bufq_reset(&sx->iobuf);
+    sx->start_resolving = FALSE;
+    sx->socks4a = (cf->conn->socks_proxy.proxytype == CURLPROXY_SOCKS4A);
+    sx->resolve_local = !sx->socks4a;
+    sx->presult = CURLPX_OK;
+
+    /* SOCKS4 can only do IPv4, insist! */
+    cf->conn->ip_version = CURL_IPRESOLVE_V4;
+    CURL_TRC_CF(data, cf, "SOCKS4%s communication to%s %s:%d",
+                sx->socks4a ? "a" : "",
+                cf->conn->bits.httpproxy ? " HTTP proxy" : "",
                 sx->hostname, sx->remote_port);
 
+    /*
+     * Compose socks4 request
+     *
+     * Request format
+     *
+     *     +----+----+----+----+----+----+----+----+----+----+....+----+
+     *     | VN | CD | DSTPORT |      DSTIP        | USERID       |NULL|
+     *     +----+----+----+----+----+----+----+----+----+----+....+----+
+     * # of bytes:  1    1      2              4           variable       1
+     */
+    presult = socks4_req_add_hd(sx, data);
+    if(presult)
+      return socks_failed(sx, cf, data, presult);
+
+    /* DNS resolve only for SOCKS4, not SOCKS4a */
+    if(!sx->resolve_local) {
+      /* socks4a, not resolving locally, sends the hostname.
+       * add an invalid address + user + hostname */
+      unsigned char buf[4] = { 0, 0, 0, 1 };
+      size_t hlen =  strlen(sx->hostname) + 1; /* including NUL */
+
+      if(hlen > 255) {
+        failf(data, "SOCKS4: too long hostname");
+        return socks_failed(sx, cf, data, CURLPX_LONG_HOSTNAME);
+      }
+      result = Curl_bufq_write(&sx->iobuf, buf, 4, &nwritten);
+      if(result || (nwritten != 4))
+        return socks_failed(sx, cf, data, CURLPX_SEND_REQUEST);
+      presult = socks4_req_add_user(sx, data);
+      if(presult)
+        return socks_failed(sx, cf, data, presult);
+      result = Curl_bufq_cwrite(&sx->iobuf, sx->hostname, hlen, &nwritten);
+      if(result || (nwritten != hlen))
+        return socks_failed(sx, cf, data, CURLPX_SEND_REQUEST);
+      /* request complete */
+      sxstate(sx, cf, data, SOCKS4_ST_SEND);
+      goto process_state;
+    }
+    sx->start_resolving = TRUE;
+    sxstate(sx, cf, data, SOCKS4_ST_RESOLVING);
+    FALLTHROUGH();
+
+  case SOCKS4_ST_RESOLVING:
+    presult = socks4_resolving(sx, cf, data, &done);
+    if(presult)
+      return socks_failed(sx, cf, data, presult);
+    if(!done)
+      return CURLPX_OK;
+    /* append user */
+    presult = socks4_req_add_user(sx, data);
+    if(presult)
+      return socks_failed(sx, cf, data, presult);
+    sxstate(sx, cf, data, SOCKS4_ST_SEND);
+    FALLTHROUGH();
+
+  case SOCKS4_ST_SEND:
+    presult = socks_flush(sx, cf, data, &done);
+    if(presult)
+      return socks_failed(sx, cf, data, presult);
+    else if(!done)
+      return CURLPX_OK;
+    sxstate(sx, cf, data, SOCKS4_ST_RECV);
+    FALLTHROUGH();
+
+  case SOCKS4_ST_RECV:
+    /* Receive 8 byte response */
+    presult = socks_recv(sx, cf, data, 8, &done);
+    if(presult)
+      return socks_failed(sx, cf, data, presult);
+    else if(!done)
+      return CURLPX_OK;
+    presult = socks4_check_resp(sx, cf, data);
+    if(presult)
+      return socks_failed(sx, cf, data, presult);
+    sxstate(sx, cf, data, SOCKS_ST_SUCCESS);
+    FALLTHROUGH();
+
+  case SOCKS_ST_SUCCESS:
+    return CURLPX_OK;
+
+  case SOCKS_ST_FAILED:
+    DEBUGASSERT(sx->presult);
+    return sx->presult;
+
+  default:
+    DEBUGASSERT(0);
+    return socks_failed(sx, cf, data, CURLPX_SEND_REQUEST);
+  }
+}
+
+static CURLproxycode socks5_req0_init(struct Curl_cfilter *cf,
+                                      struct socks_state *sx,
+                                      struct Curl_easy *data)
+{
+  const unsigned char auth = data->set.socks5auth;
+  unsigned char req[5]; /* version + len + 3 possible auth methods */
+  unsigned char nauths;
+  size_t req_len, nwritten;
+  CURLcode result;
+
+  (void)cf;
   /* RFC1928 chapter 5 specifies max 255 chars for domain name in packet */
-  if(!socks5_resolve_local && hostname_len > 255) {
+  if(!sx->resolve_local && strlen(sx->hostname) > 255) {
     failf(data, "SOCKS5: the destination hostname is too long to be "
           "resolved remotely by the proxy.");
     return CURLPX_LONG_HOSTNAME;
@@ -556,27 +618,73 @@ static CURLproxycode socks5_init(struct Curl_cfilter *cf,
     /* disable username/password auth */
     sx->proxy_user = NULL;
 
-  if(!sx->outstanding) {
-    size_t idx = 0;
-    socksreq[idx++] = 5;   /* version */
-    idx++;                 /* number of authentication methods */
-    socksreq[idx++] = 0;   /* no authentication */
+  req[0] = 5;   /* version */
+  nauths = 1;
+  req[1 + nauths] = 0;   /* 1. no authentication */
 #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
-    if(auth & CURLAUTH_GSSAPI)
-      socksreq[idx++] = 1; /* GSS-API */
+  if(auth & CURLAUTH_GSSAPI) {
+    ++nauths;
+    req[1 + nauths] = 1; /* GSS-API */
+  }
 #endif
-    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;
+  if(sx->proxy_user) {
+    ++nauths;
+    req[1 + nauths] = 2; /* username/password */
   }
+  req[1] = nauths;
+  req_len = 2 + nauths;
+
+  result = Curl_bufq_write(&sx->iobuf, req, req_len, &nwritten);
+  if(result || (nwritten != req_len))
+    return CURLPX_SEND_REQUEST;
+  return CURLPX_OK;
+}
+
+static CURLproxycode socks5_check_resp0(struct socks_state *sx,
+                                        struct Curl_cfilter *cf,
+                                        struct Curl_easy *data)
+{
+  const unsigned char *resp;
+  unsigned char auth_mode;
+  size_t rlen;
 
-  return socks_state_send(cf, sx, data, CURLPX_SEND_CONNECT,
-                          "initial SOCKS5 request");
+  if(!Curl_bufq_peek(&sx->iobuf, &resp, &rlen) || rlen < 2) {
+    failf(data, "SOCKS5 initial reply is incomplete.");
+    return CURLPX_RECV_CONNECT;
+  }
+
+  if(resp[0] != 5) {
+    failf(data, "Received invalid version in initial SOCKS5 response.");
+    return CURLPX_BAD_VERSION;
+  }
+
+  auth_mode = resp[1];
+  Curl_bufq_reset(&sx->iobuf);
+
+  switch(auth_mode) {
+  case 0:
+    /* DONE! No authentication needed. Send request. */
+    sxstate(sx, cf, data, SOCKS5_ST_REQ1_INIT);
+    return CURLPX_OK;
+  case 1:
+    if(data->set.socks5auth & CURLAUTH_GSSAPI) {
+      sxstate(sx, cf, data, SOCKS5_ST_GSSAPI_INIT);
+      return CURLPX_OK;
+    }
+    failf(data,
+          "SOCKS5 GSSAPI per-message authentication is not enabled.");
+    return CURLPX_GSSAPI_PERMSG;
+  case 2:
+    /* regular name + password authentication */
+    sxstate(sx, cf, data, SOCKS5_ST_AUTH_INIT);
+    return CURLPX_OK;
+  case 255:
+    failf(data, "No authentication method was acceptable.");
+    return CURLPX_NO_AUTH;
+  default:
+    failf(data, "Unknown SOCKS5 mode attempted to be used by server.");
+    return CURLPX_UNKNOWN_MODE;
+  }
 }
 
 static CURLproxycode socks5_auth_init(struct Curl_cfilter *cf,
@@ -584,17 +692,22 @@ static CURLproxycode socks5_auth_init(struct Curl_cfilter *cf,
                                       struct Curl_easy *data)
 {
   /* Needs username and password */
-  size_t proxy_user_len, proxy_password_len;
-  size_t len = 0;
-  unsigned char *socksreq = sx->buffer;
+  size_t ulen = 0, plen = 0, nwritten;
+  unsigned char buf[2];
+  CURLcode result;
 
   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;
+    ulen = strlen(sx->proxy_user);
+    plen = strlen(sx->proxy_password);
+    /* the lengths must fit in a single byte */
+    if(ulen > 255) {
+      failf(data, "Excessive username length for proxy auth");
+      return CURLPX_LONG_USER;
+    }
+    if(plen > 255) {
+      failf(data, "Excessive password length for proxy auth");
+      return CURLPX_LONG_PASSWD;
+    }
   }
 
   /*   username/password request looks like
@@ -604,499 +717,494 @@ static CURLproxycode socks5_auth_init(struct Curl_cfilter *cf,
    * | 1  |  1   | 1 to 255 |  1   | 1 to 255 |
    * +----+------+----------+------+----------+
    */
-  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);
+  buf[0] = 1;    /* username/pw subnegotiation version */
+  buf[1] = (unsigned char)ulen;
+  result = Curl_bufq_write(&sx->iobuf, buf, 2, &nwritten);
+  if(result || (nwritten != 2))
+    return CURLPX_SEND_REQUEST;
+  if(ulen) {
+    result = Curl_bufq_cwrite(&sx->iobuf, sx->proxy_user, ulen, &nwritten);
+    if(result || (nwritten != ulen))
+      return CURLPX_SEND_REQUEST;
   }
-  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);
+  buf[0] = (unsigned char) plen;
+  result = Curl_bufq_write(&sx->iobuf, buf, 1, &nwritten);
+  if(result || (nwritten != 1))
+    return CURLPX_SEND_REQUEST;
+  if(plen) {
+    result = Curl_bufq_cwrite(&sx->iobuf, sx->proxy_password, plen, &nwritten);
+    if(result || (nwritten != plen))
+      return CURLPX_SEND_REQUEST;
   }
-  len += proxy_password_len;
-  sxstate(sx, cf, data, CONNECT_AUTH_SEND);
-  DEBUGASSERT(len <= sizeof(sx->buffer));
-  sx->outstanding = len;
-  sx->outp = socksreq;
+  sxstate(sx, cf, data, SOCKS5_ST_AUTH_SEND);
   return CURLPX_OK;
 }
 
-/*
- * This function logs in to a SOCKS5 proxy and sends the specifics to the final
- * destination server.
- */
-static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf,
-                               struct socks_state *sx,
-                               struct Curl_easy *data)
+static CURLproxycode socks5_check_auth_resp(struct socks_state *sx,
+                                            struct Curl_cfilter *cf,
+                                            struct Curl_easy *data)
 {
-  /*
-    According to the RFC1928, section "6. Replies". This is what a SOCK5
-    replies:
+  const unsigned char *resp;
+  unsigned char auth_status;
+  size_t rlen;
 
-        +----+-----+-------+------+----------+----------+
-        |VER | REP |  RSV  | ATYP | BND.ADDR | BND.PORT |
-        +----+-----+-------+------+----------+----------+
-        | 1  |  1  | X'00' |  1   | Variable |    2     |
-        +----+-----+-------+------+----------+----------+
+  (void)cf;
+  if(!Curl_bufq_peek(&sx->iobuf, &resp, &rlen) || rlen < 2) {
+    failf(data, "SOCKS5 sub-negotiation response incomplete.");
+    return CURLPX_RECV_CONNECT;
+  }
 
-    Where:
+  /* ignore the first (VER) byte */
+  auth_status = resp[1];
+  Curl_bufq_reset(&sx->iobuf);
 
-    o  VER    protocol version: X'05'
-    o  REP    Reply field:
-    o  X'00' succeeded
-  */
-  struct connectdata *conn = cf->conn;
-  unsigned char *socksreq = sx->buffer;
+  if(auth_status) {
+    failf(data, "User was rejected by the SOCKS5 server (%d %d).",
+          resp[0], resp[1]);
+    return CURLPX_USER_REJECTED;
+  }
+  return CURLPX_OK;
+}
+
+static CURLproxycode socks5_req1_init(struct socks_state *sx,
+                                      struct Curl_cfilter *cf,
+                                      struct Curl_easy *data)
+{
+  unsigned char req[5];
+  unsigned char ipbuf[16];
+  const unsigned char *destination;
+  unsigned char desttype, destlen, hdlen;
+  size_t nwritten;
   CURLcode result;
-  CURLproxycode presult;
-  bool socks5_resolve_local =
-    (conn->socks_proxy.proxytype == CURLPROXY_SOCKS5);
-  const size_t hostname_len = strlen(sx->hostname);
-  size_t len = 0;
-  bool allow_gssapi = FALSE;
-  struct Curl_dns_entry *dns = NULL;
 
-  switch(sx->state) {
-  case CONNECT_SOCKS_INIT:
-    presult = socks5_init(cf, sx, data, socks5_resolve_local, hostname_len);
-    if(presult || sx->outstanding)
-      return presult;
-    sxstate(sx, cf, 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 defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
-    if(data->set.socks5auth & CURLAUTH_GSSAPI)
-      allow_gssapi = TRUE;
-#endif
-    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, cf, data, CONNECT_REQ_INIT);
-      goto CONNECT_REQ_INIT;
-    }
-    else if(socksreq[1] == 2) {
-      /* regular name + password authentication */
-      sxstate(sx, cf, 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, cf, 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;
+  req[0] = 5; /* version (SOCKS5) */
+  req[1] = 1; /* connect */
+  req[2] = 0; /* must be zero */
+  if(sx->resolve_local) {
+    /* rest of request is added after resolving */
+    result = Curl_bufq_write(&sx->iobuf, req, 3, &nwritten);
+    if(result || (nwritten != 3))
+      return CURLPX_SEND_REQUEST;
+    return CURLPX_OK;
+  }
+
+  /* remote resolving, send what type+addr/string to resolve */
+#ifdef USE_IPV6
+  if(cf->conn->bits.ipv6_ip) {
+    desttype = 4;
+    destination = ipbuf;
+    destlen = 16;
+    if(curlx_inet_pton(AF_INET6, sx->hostname, ipbuf) != 1)
+      return CURLPX_BAD_ADDRESS_TYPE;
+  }
+  else
 #endif
+  if(curlx_inet_pton(AF_INET, sx->hostname, ipbuf) == 1) {
+    desttype = 1;
+    destination = ipbuf;
+    destlen = 4;
+  }
+  else {
+    const size_t hostname_len = strlen(sx->hostname);
+    desttype = 3;
+    destination = (const unsigned char *)sx->hostname;
+    destlen = (unsigned char) hostname_len; /* one byte length */
+  }
 
-  default: /* do nothing! */
-    break;
+  req[3] = desttype;
+  req[4] = destlen;
+  hdlen = (desttype == 3) ? 5 : 4; /* no length byte for ip addresses */
+  result = Curl_bufq_write(&sx->iobuf, req, hdlen, &nwritten);
+  if(result || (nwritten != hdlen))
+    return CURLPX_SEND_REQUEST;
+  result = Curl_bufq_write(&sx->iobuf, destination, destlen, &nwritten);
+  if(result || (nwritten != destlen))
+    return CURLPX_SEND_REQUEST;
+  /* PORT MSB+LSB */
+  req[0] = (unsigned char)((sx->remote_port >> 8) & 0xff);
+  req[1] = (unsigned char)(sx->remote_port & 0xff);
+  result = Curl_bufq_write(&sx->iobuf, req, 2, &nwritten);
+  if(result || (nwritten != 2))
+    return CURLPX_SEND_REQUEST;
+  CURL_TRC_CF(data, cf, "SOCKS5 connect to %s:%d (remotely resolved)",
+              sx->hostname, sx->remote_port);
+  return CURLPX_OK;
+}
 
-CONNECT_AUTH_INIT:
-  case CONNECT_AUTH_INIT:
-    presult = socks5_auth_init(cf, sx, data);
-    if(presult)
-      return presult;
-    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, cf, 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;
-    }
+static CURLproxycode socks5_resolving(struct socks_state *sx,
+                                      struct Curl_cfilter *cf,
+                                      struct Curl_easy *data,
+                                      bool *done)
+{
+  struct Curl_dns_entry *dns = NULL;
+  struct Curl_addrinfo *hp = NULL;
+  char dest[MAX_IPADR_LEN];  /* printable address */
+  unsigned char *destination = NULL;
+  unsigned char desttype = 1, destlen = 4;
+  unsigned char req[2];
+  CURLcode result;
+  CURLproxycode presult = CURLPX_OK;
+  size_t nwritten;
 
-    /* Everything is good so far, user was authenticated! */
-    sxstate(sx, cf, data, CONNECT_REQ_INIT);
-    FALLTHROUGH();
-  case CONNECT_REQ_INIT:
-CONNECT_REQ_INIT:
-    if(socks5_resolve_local) {
-      result = Curl_resolv(data, sx->hostname, sx->remote_port,
-                           cf->conn->ip_version, TRUE, &dns);
-
-      if(result == CURLE_AGAIN) {
-        sxstate(sx, cf, data, CONNECT_RESOLVING);
-        return CURLPX_OK;
-      }
-      else if(result)
-        return CURLPX_RESOLVE_HOST;
-      sxstate(sx, cf, data, CONNECT_RESOLVED);
-      goto CONNECT_RESOLVED;
-    }
-    goto CONNECT_RESOLVE_REMOTE;
+  *done = FALSE;
+  if(sx->start_resolving) {
+    /* need to resolve hostname to add destination address */
+    sx->start_resolving = FALSE;
 
-  case CONNECT_RESOLVING:
+    result = Curl_resolv(data, sx->hostname, sx->remote_port,
+                         cf->conn->ip_version, TRUE, &dns);
+    if(result == CURLE_AGAIN) {
+      CURL_TRC_CF(data, cf, "SOCKS5 non-blocking resolve of %s",
+                  sx->hostname);
+      return CURLPX_OK;
+    }
+    else if(result)
+      return CURLPX_RESOLVE_HOST;
+  }
+  else {
     /* check if we have the name resolved by now */
     result = Curl_resolv_check(data, &dns);
-    if(!dns) {
-      if(result)
-        return CURLPX_RESOLVE_HOST;
+    if(!result && !dns)
       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;
+  }
+
+  if(result || !dns) {
+    failf(data, "Failed to resolve \"%s\" for SOCKS5 connect.",
+          sx->hostname);
+    presult = CURLPX_RESOLVE_HOST;
+    goto out;
+  }
+
+  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;
-    }
+  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;
-    }
+  if(!hp) {
+    failf(data, "Failed to resolve \"%s\" for SOCKS5 connect.",
+          sx->hostname);
+    presult = CURLPX_RESOLVE_HOST;
+    goto out;
+  }
 
-    Curl_printable_address(hp, dest, sizeof(dest));
+  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 */
+  if(hp->ai_family == AF_INET) {
+    struct sockaddr_in *saddr_in;
+    desttype = 1; /* ATYP: IPv4 = 1 */
+    destlen = 4;
+    saddr_in = (struct sockaddr_in *)(void *)hp->ai_addr;
+    destination = (unsigned char *)&saddr_in->sin_addr.s_addr;
+    CURL_TRC_CF(data, cf, "SOCKS5 connect to %s:%d (locally resolved)",
+                dest, sx->remote_port);
+  }
+#ifdef USE_IPV6
+  else if(hp->ai_family == AF_INET6) {
+    struct sockaddr_in6 *saddr_in6;
+    desttype = 4; /* ATYP: IPv6 = 4 */
+    destlen = 16;
+    saddr_in6 = (struct sockaddr_in6 *)(void *)hp->ai_addr;
+    destination = (unsigned char *)&saddr_in6->sin6_addr.s6_addr;
+    CURL_TRC_CF(data, cf, "SOCKS5 connect to [%s]:%d (locally resolved)",
+                dest, sx->remote_port);
+  }
+#endif
 
-      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(!destination) {
+    failf(data, "SOCKS5 connection to %s not supported", dest);
+    presult = CURLPX_RESOLVE_HOST;
+    goto out;
+  }
 
-      CURL_TRC_CF(data, cf, "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];
-      }
+  req[0] = desttype;
+  result = Curl_bufq_write(&sx->iobuf, req, 1, &nwritten);
+  if(result || (nwritten != 1)) {
+    presult = CURLPX_SEND_REQUEST;
+    goto out;
+  }
+  result = Curl_bufq_write(&sx->iobuf, destination, destlen, &nwritten);
+  if(result || (nwritten != destlen)) {
+    presult = CURLPX_SEND_REQUEST;
+    goto out;
+  }
+  /* PORT MSB+LSB */
+  req[0] = (unsigned char)((sx->remote_port >> 8) & 0xffu);
+  req[1] = (unsigned char)(sx->remote_port & 0xffu);
+  result = Curl_bufq_write(&sx->iobuf, req, 2, &nwritten);
+  if(result || (nwritten != 2)) {
+    presult = CURLPX_SEND_REQUEST;
+    goto out;
+  }
 
-      CURL_TRC_CF(data, cf, "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);
+out:
+  if(dns)
+    Curl_resolv_unlink(data, &dns);
+  *done = (presult == CURLPX_OK);
+  return presult;
+}
+
+static CURLproxycode socks5_recv_resp1(struct socks_state *sx,
+                                       struct Curl_cfilter *cf,
+                                       struct Curl_easy *data,
+                                       bool *done)
+{
+  const unsigned char *resp;
+  size_t rlen, resp_len = 8; /* minimum response length */
+  CURLproxycode presult;
+
+  presult = socks_recv(sx, cf, data, resp_len, done);
+  if(presult)
+    return presult;
+  else if(!*done)
+    return CURLPX_OK;
+
+  if(!Curl_bufq_peek(&sx->iobuf, &resp, &rlen) || rlen < resp_len) {
+    failf(data, "SOCKS5 response is incomplete.");
+    return CURLPX_RECV_CONNECT;
+  }
+
+  /* Response packet includes BND.ADDR is variable length parameter by RFC
+     1928, so the response packet MUST 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
+  */
+  if(resp[0] != 5) { /* version */
+    failf(data, "SOCKS5 reply has wrong version, version should be 5.");
+    return CURLPX_BAD_VERSION;
+  }
+  else if(resp[1]) { /* Anything besides 0 is an error */
+    CURLproxycode rc = CURLPX_REPLY_UNASSIGNED;
+    int code = resp[1];
+    failf(data, "cannot complete SOCKS5 connection to %s. (%d)",
+          sx->hostname, code);
+    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;
+  }
 
-    Curl_resolv_unlink(data, &dns); /* not used anymore from now on */
-    goto CONNECT_REQ_SEND;
+  /* Calculate real packet size */
+  switch(resp[3]) {
+  case 1: /* IPv4 */
+    resp_len = 4 + 4 + 2;
+    break;
+  case 3: /* domain name */
+    resp_len = 4 + 1 + resp[4] + 2; /* header, var length, var bytes, port */
+    break;
+  case 4: /* IPv6 */
+    resp_len = 4 + 16 + 2;
+    break;
+  default:
+    failf(data, "SOCKS5 reply has wrong address type.");
+    return CURLPX_BAD_ADDRESS_TYPE;
   }
-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(curlx_inet_pton(AF_INET6, sx->hostname, ip6) != 1)
-          return CURLPX_BAD_ADDRESS_TYPE;
-        socksreq[len++] = 4;
-        memcpy(&socksreq[len], ip6, sizeof(ip6));
-        len += sizeof(ip6);
-      }
-      else
-#endif
-      if(curlx_inet_pton(AF_INET, sx->hostname, ip4) == 1) {
-        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;
-      }
-      CURL_TRC_CF(data, cf, "SOCKS5 connect to %s:%d (remotely resolved)",
+
+  /* receive the rest of the response */
+  presult = socks_recv(sx, cf, data, resp_len, done);
+  if(presult)
+    return presult;
+  else if(!*done)
+    return CURLPX_OK;
+
+  if(!Curl_bufq_peek(&sx->iobuf, &resp, &rlen) || rlen < resp_len) {
+    failf(data, "SOCKS5 response is incomplete.");
+    return CURLPX_RECV_CONNECT;
+  }
+  /* got it all */
+  *done = TRUE;
+  return CURLPX_OK;
+}
+
+/*
+ * This function logs in to a SOCKS5 proxy and sends the specifics to the final
+ * destination server.
+ */
+static CURLproxycode socks5_connect(struct Curl_cfilter *cf,
+                                    struct socks_state *sx,
+                                    struct Curl_easy *data)
+{
+  CURLproxycode presult;
+  bool done;
+
+process_state:
+  switch(sx->state) {
+  case SOCKS_ST_INIT:
+    sx->version = 5;
+    sx->resolve_local = (cf->conn->socks_proxy.proxytype == CURLPROXY_SOCKS5);
+    sxstate(sx, cf, data, SOCKS5_ST_START);
+    FALLTHROUGH();
+
+  case SOCKS5_ST_START:
+    if(cf->conn->bits.httpproxy)
+      CURL_TRC_CF(data, cf, "SOCKS5: connecting to HTTP proxy %s port %d",
                   sx->hostname, sx->remote_port);
-    }
+    presult = socks5_req0_init(cf, sx, data);
+    if(presult)
+      return socks_failed(sx, cf, data, presult);
+    sxstate(sx, cf, data, SOCKS5_ST_REQ0_SEND);
     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);
+  case SOCKS5_ST_REQ0_SEND:
+    presult = socks_flush(sx, cf, data, &done);
+    if(presult)
+      return socks_failed(sx, cf, data, presult);
+    else if(!done)
+      return CURLPX_OK;
+    /* done sending! */
+    sxstate(sx, cf, data, SOCKS5_ST_RESP0_RECV);
+    FALLTHROUGH();
+
+  case SOCKS5_ST_RESP0_RECV:
+    presult = socks_recv(sx, cf, data, 2, &done);
+    if(presult)
+      return socks_failed(sx, cf, data, presult);
+    else if(!done)
+      return CURLPX_OK;
+    presult = socks5_check_resp0(sx, cf, data);
+    if(presult)
+      return socks_failed(sx, cf, data, presult);
+    /* socks5_check_resp0() sets next socks state */
+    goto process_state;
 
+  case SOCKS5_ST_GSSAPI_INIT: {
 #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;
+    /* GSSAPI stuff done non-blocking */
+    CURLcode result = Curl_SOCKS5_gssapi_negotiate(cf, data);
+    if(result) {
+      failf(data, "Unable to negotiate SOCKS5 GSS-API context.");
+      return CURLPX_GSSAPI;
     }
+    sxstate(sx, cf, data, SOCKS5_ST_REQ1_INIT);
+    goto process_state;
+#else
+    failf(data,
+          "SOCKS5 GSSAPI per-message authentication is not supported.");
+    return socks_failed(sx, cf, data, CURLPX_GSSAPI_PERMSG);
 #endif
-    sx->outp = socksreq;
-    DEBUGASSERT(len <= sizeof(sx->buffer));
-    sx->outstanding = len;
-    sxstate(sx, cf, data, CONNECT_REQ_SENDING);
+  }
+
+  case SOCKS5_ST_AUTH_INIT:
+    presult = socks5_auth_init(cf, sx, data);
+    if(presult)
+      return socks_failed(sx, cf, data, presult);
+    sxstate(sx, cf, data, SOCKS5_ST_AUTH_SEND);
     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 */
+
+  case SOCKS5_ST_AUTH_SEND:
+    presult = socks_flush(sx, cf, data, &done);
+    if(presult)
+      return socks_failed(sx, cf, data, presult);
+    else if(!done)
       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, cf, data, CONNECT_REQ_READ);
+    sxstate(sx, cf, data, SOCKS5_ST_AUTH_RECV);
     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 */
+
+  case SOCKS5_ST_AUTH_RECV:
+    presult = socks_recv(sx, cf, data, 2, &done);
+    if(presult)
+      return socks_failed(sx, cf, data, presult);
+    else if(!done)
       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;
-    }
+    presult = socks5_check_auth_resp(sx, cf, data);
+    if(presult)
+      return socks_failed(sx, cf, data, presult);
+    /* Everything is good so far, user was authenticated! */
+    sxstate(sx, cf, data, SOCKS5_ST_REQ1_INIT);
+    FALLTHROUGH();
 
-    /* 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;
+  case SOCKS5_ST_REQ1_INIT:
+    presult = socks5_req1_init(sx, cf, data);
+    if(presult)
+      return socks_failed(sx, cf, data, presult);
+    if(!sx->resolve_local) {
+      /* we do not resolve, request is complete */
+      sxstate(sx, cf, data, SOCKS5_ST_REQ1_SEND);
+      goto process_state;
     }
+    sx->start_resolving = TRUE;
+    sxstate(sx, cf, data, SOCKS5_ST_RESOLVING);
+    FALLTHROUGH();
 
-    /* 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, cf, data, CONNECT_REQ_READ_MORE);
-      }
-      else {
-        sxstate(sx, cf, data, CONNECT_DONE);
-        break;
-      }
+  case SOCKS5_ST_RESOLVING:
+    presult = socks5_resolving(sx, cf, data, &done);
+    if(presult)
+      return socks_failed(sx, cf, data, presult);
+    if(!done)
+      return CURLPX_OK;
+    sxstate(sx, cf, data, SOCKS5_ST_REQ1_SEND);
+    FALLTHROUGH();
+
+  case SOCKS5_ST_REQ1_SEND:
+    presult = socks_flush(sx, cf, data, &done);
+    if(presult)
+      return socks_failed(sx, cf, data, presult);
+    else if(!done)
+      return CURLPX_OK;
 #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
+    if(cf->conn->socks5_gssapi_enctype) {
+      failf(data, "SOCKS5 GSS-API protection not yet implemented.");
+      return CURLPX_GSSAPI_PROTECTION;
     }
 #endif
+    sxstate(sx, cf, data, SOCKS5_ST_RESP1_RECV);
     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, cf, data, CONNECT_DONE);
-  }
-  CURL_TRC_CF(data, cf, "SOCKS5 request granted.");
 
-  return CURLPX_OK; /* Proxy was successful! */
-}
-
-static CURLcode connect_SOCKS(struct Curl_cfilter *cf,
-                              struct socks_state *sxstate,
-                              struct Curl_easy *data)
-{
-  CURLcode result = CURLE_OK;
-  CURLproxycode pxresult = CURLPX_OK;
-  struct connectdata *conn = cf->conn;
+  case SOCKS5_ST_RESP1_RECV:
+    presult = socks5_recv_resp1(sx, cf, data, &done);
+    if(presult)
+      return socks_failed(sx, cf, data, presult);
+    if(!done)
+      return CURLPX_OK;
+    CURL_TRC_CF(data, cf, "SOCKS5 request granted.");
+    sxstate(sx, cf, data, SOCKS_ST_SUCCESS);
+    FALLTHROUGH();
 
-  switch(conn->socks_proxy.proxytype) {
-  case CURLPROXY_SOCKS5:
-  case CURLPROXY_SOCKS5_HOSTNAME:
-    pxresult = do_SOCKS5(cf, sxstate, data);
-    break;
+  case SOCKS_ST_SUCCESS:
+    return CURLPX_OK;
 
-  case CURLPROXY_SOCKS4:
-  case CURLPROXY_SOCKS4A:
-    pxresult = do_SOCKS4(cf, sxstate, data);
-    break;
+  case SOCKS_ST_FAILED:
+    DEBUGASSERT(sx->presult);
+    return sx->presult;
 
   default:
-    failf(data, "unknown proxytype option given");
-    result = CURLE_COULDNT_CONNECT;
-  } /* switch proxytype */
-  if(pxresult) {
-    result = CURLE_PROXY;
-    data->info.pxcode = pxresult;
+    DEBUGASSERT(0);
+    return socks_failed(sx, cf, data, CURLPX_SEND_REQUEST);
   }
-
-  return result;
 }
 
 static void socks_proxy_cf_free(struct Curl_cfilter *cf)
 {
   struct socks_state *sxstate = cf->ctx;
   if(sxstate) {
+    Curl_bufq_free(&sxstate->iobuf);
     free(sxstate);
     cf->ctx = NULL;
   }
@@ -1117,6 +1225,7 @@ static CURLcode socks_proxy_cf_connect(struct Curl_cfilter *cf,
   struct connectdata *conn = cf->conn;
   int sockindex = cf->sockindex;
   struct socks_state *sx = cf->ctx;
+  CURLproxycode pxresult = CURLPX_OK;
 
   if(cf->connected) {
     *done = TRUE;
@@ -1128,17 +1237,13 @@ static CURLcode socks_proxy_cf_connect(struct Curl_cfilter *cf,
     return result;
 
   if(!sx) {
-    sx = calloc(1, sizeof(*sx));
+    cf->ctx = sx = calloc(1, sizeof(*sx));
     if(!sx)
       return CURLE_OUT_OF_MEMORY;
-    cf->ctx = sx;
-  }
 
-  if(sx->state == CONNECT_INIT) {
     /* for the secondary socket (FTP), use the "connect to host"
      * but ignore the "connect to port" (use the secondary port)
      */
-    sxstate(sx, cf, data, CONNECT_SOCKS_INIT);
     sx->hostname =
       conn->bits.httpproxy ?
       conn->http_proxy.host.name :
@@ -1153,29 +1258,54 @@ static CURLcode socks_proxy_cf_connect(struct Curl_cfilter *cf,
       conn->remote_port;
     sx->proxy_user = conn->socks_proxy.user;
     sx->proxy_password = conn->socks_proxy.passwd;
+    Curl_bufq_init2(&sx->iobuf, SOCKS_CHUNK_SIZE, SOCKS_CHUNKS,
+                    BUFQ_OPT_SOFT_LIMIT);
+  }
+
+  switch(conn->socks_proxy.proxytype) {
+  case CURLPROXY_SOCKS5:
+  case CURLPROXY_SOCKS5_HOSTNAME:
+    pxresult = socks5_connect(cf, sx, data);
+    break;
+
+  case CURLPROXY_SOCKS4:
+  case CURLPROXY_SOCKS4A:
+    pxresult = socks4_connect(cf, sx, data);
+    break;
+
+  default:
+    failf(data, "unknown proxytype option given");
+    result = CURLE_COULDNT_CONNECT;
+    goto out;
+  }
+
+  if(pxresult) {
+    result = CURLE_PROXY;
+    data->info.pxcode = pxresult;
+    goto out;
   }
+  else if(sx->state != SOCKS_ST_SUCCESS)
+    goto out;
 
-  result = connect_SOCKS(cf, sx, data);
-  if(!result && sx->state == CONNECT_DONE) {
-    cf->connected = TRUE;
 #ifndef CURL_DISABLE_VERBOSE_STRINGS
-    if(Curl_trc_is_verbose(data)) {
-      struct ip_quadruple ipquad;
-      bool is_ipv6;
-      result = Curl_conn_cf_get_ip_info(cf->next, data, &is_ipv6, &ipquad);
-      if(result)
-        return result;
-      infof(data, "Opened %sSOCKS connection from %s port %u to %s port %u "
-            "(via %s port %u)",
-            (sockindex == SECONDARYSOCKET) ? "2nd " : "",
-            ipquad.local_ip, ipquad.local_port,
-            sx->hostname, sx->remote_port,
-            ipquad.remote_ip, ipquad.remote_port);
-    }
-#endif
-    socks_proxy_cf_free(cf);
+  if(Curl_trc_is_verbose(data)) {
+    struct ip_quadruple ipquad;
+    bool is_ipv6;
+    result = Curl_conn_cf_get_ip_info(cf->next, data, &is_ipv6, &ipquad);
+    if(result)
+      return result;
+    infof(data, "Opened %sSOCKS connection from %s port %u to %s port %u "
+          "(via %s port %u)",
+          (sockindex == SECONDARYSOCKET) ? "2nd " : "",
+          ipquad.local_ip, ipquad.local_port,
+          sx->hostname, sx->remote_port,
+          ipquad.remote_ip, ipquad.remote_port);
   }
+#endif
+  socks_proxy_cf_free(cf);
+  cf->connected = TRUE;
 
+out:
   *done = cf->connected;
   return result;
 }
@@ -1192,15 +1322,16 @@ static CURLcode socks_cf_adjust_pollset(struct Curl_cfilter *cf,
      * to wait on, we determine what to wait for. */
     curl_socket_t sock = Curl_conn_cf_get_socket(cf, data);
     switch(sx->state) {
-    case CONNECT_RESOLVING:
-    case CONNECT_SOCKS_READ:
-    case CONNECT_AUTH_READ:
-    case CONNECT_REQ_READ:
-    case CONNECT_REQ_READ_MORE:
-      result = Curl_pollset_set_in_only(data, ps, sock);
+    case SOCKS4_ST_SEND:
+    case SOCKS5_ST_REQ0_SEND:
+    case SOCKS5_ST_AUTH_SEND:
+    case SOCKS5_ST_REQ1_SEND:
+      CURL_TRC_CF(data, cf, "adjust pollset out (%d)", sx->state);
+      result = Curl_pollset_set_out_only(data, ps, sock);
       break;
     default:
-      result = Curl_pollset_set_out_only(data, ps, sock);
+      CURL_TRC_CF(data, cf, "adjust pollset in (%d)", sx->state);
+      result = Curl_pollset_set_in_only(data, ps, sock);
       break;
     }
   }
index 86e4f9ed06c3c1ecd4c6995b922ddd8efcee9b5a..3fc86015044b3abf43ff427fa54353e08b8e7a2a 100644 (file)
@@ -23,6 +23,7 @@
  ***************************************************************************/
 #include "first.h"
 
+#include "testtrace.h"
 #include "memdebug.h"
 
 static CURLcode test_lib564(const char *URL)
@@ -32,6 +33,9 @@ static CURLcode test_lib564(const char *URL)
   int running;
   CURLM *m = NULL;
 
+  debug_config.nohex = TRUE;
+  debug_config.tracetime = TRUE;
+
   start_test_timing();
 
   global_init(CURL_GLOBAL_ALL);
@@ -39,6 +43,8 @@ static CURLcode test_lib564(const char *URL)
   easy_init(curl);
 
   easy_setopt(curl, CURLOPT_URL, URL);
+  easy_setopt(curl, CURLOPT_DEBUGDATA, &debug_config);
+  easy_setopt(curl, CURLOPT_DEBUGFUNCTION, libtest_debug_cb);
   easy_setopt(curl, CURLOPT_VERBOSE, 1L);
   easy_setopt(curl, CURLOPT_PROXY, libtest_arg2);
   easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS4);