finally finish implementing SOCKS5 proxies in ecore-con.
authordiscomfitor <discomfitor@7cbeb6ba-43b4-40fd-8cce-4c39aea84d33>
Wed, 15 Feb 2012 05:53:50 +0000 (05:53 +0000)
committerdiscomfitor <discomfitor@7cbeb6ba-43b4-40fd-8cce-4c39aea84d33>
Wed, 15 Feb 2012 05:53:50 +0000 (05:53 +0000)
SOCKS5 is different from SOCKS4 in that it supports password authentication mechanisms (GSSAPI is still on the todo) and IPV6, neither of which are possible with SOCKS4

NOTE THAT THE CMDLINE SYNTAX FOR AUTOSOCKSING HAS CHANGED!
 *   ECORE_CON_SOCKS_V4=[user@]server-port:lookup
 *   ECORE_CON_SOCKS_V5=[user@]server-port:lookup

also note that I did not implement autosocksing with password. it's just not safe.

git-svn-id: svn+ssh://svn.enlightenment.org/var/svn/e/trunk/ecore@67959 7cbeb6ba-43b4-40fd-8cce-4c39aea84d33

src/lib/ecore_con/Ecore_Con.h
src/lib/ecore_con/ecore_con.c
src/lib/ecore_con/ecore_con_private.h
src/lib/ecore_con/ecore_con_socks.c

index ea0b3f4..6f8da7b 100644 (file)
@@ -723,10 +723,13 @@ EAPI Eina_Bool         ecore_con_ssl_client_upgrade(Ecore_Con_Client *cl, Ecore_
  */
 
 EAPI Ecore_Con_Socks *ecore_con_socks4_remote_add(const char *ip, int port, const char *username);
-EAPI void             ecore_con_socks4_lookup_set(Ecore_Con_Socks *ecs, Eina_Bool enable);
-EAPI Eina_Bool        ecore_con_socks4_lookup_get(Ecore_Con_Socks *ecs);
 EAPI Eina_Bool        ecore_con_socks4_remote_exists(const char *ip, int port, const char *username);
 EAPI void             ecore_con_socks4_remote_del(const char *ip, int port, const char *username);
+EAPI Ecore_Con_Socks *ecore_con_socks5_remote_add(const char *ip, int port, const char *username, const char *password);
+EAPI Eina_Bool        ecore_con_socks5_remote_exists(const char *ip, int port, const char *username, const char *password);
+EAPI void             ecore_con_socks5_remote_del(const char *ip, int port, const char *username, const char *password);
+EAPI void             ecore_con_socks_lookup_set(Ecore_Con_Socks *ecs, Eina_Bool enable);
+EAPI Eina_Bool        ecore_con_socks_lookup_get(Ecore_Con_Socks *ecs);
 EAPI void             ecore_con_socks_bind_set(Ecore_Con_Socks *ecs, Eina_Bool is_bind);
 EAPI Eina_Bool        ecore_con_socks_bind_get(Ecore_Con_Socks *ecs);
 EAPI unsigned int     ecore_con_socks_version_get(Ecore_Con_Socks *ecs);
index de291b3..b9be145 100644 (file)
@@ -461,7 +461,7 @@ ecore_con_server_connect(Ecore_Con_Type compl_type,
                  (!ecore_con_lookup(svr->name, (Ecore_Con_Dns_Cb)ecore_con_socks_dns_cb, svr)))
                goto error;
              if (svr->ecs->lookup)
-               svr->ecs_state = ECORE_CON_SOCKS_STATE_RESOLVED;
+               svr->ecs_state = ECORE_CON_PROXY_STATE_RESOLVED;
           }
      }
    if (ecore_con_ssl_server_prepare(svr, compl_type & ECORE_CON_SSL))
@@ -1031,7 +1031,7 @@ ecore_con_event_server_del(Ecore_Con_Server *svr)
     e->server = svr;
     if (svr->ecs)
       {
-         svr->ecs_state = svr->ecs->lookup ? ECORE_CON_SOCKS_STATE_RESOLVED : ECORE_CON_SOCKS_STATE_DONE;
+         svr->ecs_state = svr->ecs->lookup ? ECORE_CON_PROXY_STATE_RESOLVED : ECORE_CON_PROXY_STATE_DONE;
          eina_stringshare_replace(&svr->proxyip, NULL);
          svr->proxyport = 0;
       }
@@ -1971,9 +1971,9 @@ _ecore_con_cl_handler(void             *data,
           ecore_con_event_server_add(svr);
         return ECORE_CALLBACK_RENEW;
      }
-   if (svr->ecs && svr->ecs_state && (svr->ecs_state < ECORE_CON_SOCKS_STATE_READ) && (!svr->ecs_buf))
+   if (svr->ecs && svr->ecs_state && (svr->ecs_state < ECORE_CON_PROXY_STATE_READ) && (!svr->ecs_buf))
      {
-        if (svr->ecs_state < ECORE_CON_SOCKS_STATE_INIT)
+        if (svr->ecs_state < ECORE_CON_PROXY_STATE_INIT)
           {
              INF("PROXY STATE++");
              svr->ecs_state++;
index 35f2310..3cd3554 100644 (file)
@@ -56,7 +56,7 @@ extern int _ecore_con_log_dom;
 
 typedef struct _Ecore_Con_Lookup Ecore_Con_Lookup;
 typedef struct _Ecore_Con_Info Ecore_Con_Info;
-typedef struct Ecore_Con_Socks_v4 Ecore_Con_Socks_v4;
+typedef struct Ecore_Con_Socks Ecore_Con_Socks_v4;
 typedef struct Ecore_Con_Socks_v5 Ecore_Con_Socks_v5;
 typedef void (*Ecore_Con_Info_Cb)(void *data, Ecore_Con_Info *infos);
 
@@ -83,13 +83,18 @@ typedef enum _Ecore_Con_Ssl_Handshake
    ECORE_CON_SSL_STATE_INIT
 } Ecore_Con_Ssl_State;
 
-typedef enum Ecore_Con_Socks_State
-{
-   ECORE_CON_SOCKS_STATE_DONE = 0,
-   ECORE_CON_SOCKS_STATE_RESOLVED,
-   ECORE_CON_SOCKS_STATE_INIT,
-   ECORE_CON_SOCKS_STATE_READ
-} Ecore_Con_Socks_State;
+typedef enum Ecore_Con_Proxy_State
+{  /* named PROXY instead of SOCKS in case some handsome and enterprising
+    * developer decides to add HTTP CONNECT support
+    */
+   ECORE_CON_PROXY_STATE_DONE = 0,
+   ECORE_CON_PROXY_STATE_RESOLVED,
+   ECORE_CON_PROXY_STATE_INIT,
+   ECORE_CON_PROXY_STATE_READ,
+   ECORE_CON_PROXY_STATE_AUTH,
+   ECORE_CON_PROXY_STATE_REQUEST,
+   ECORE_CON_PROXY_STATE_CONFIRM,
+} Ecore_Con_Proxy_State;
 
 struct _Ecore_Con_Client
 {
@@ -140,7 +145,7 @@ struct _Ecore_Con_Server
    pid_t ppid;
    /* socks */
    Ecore_Con_Socks *ecs;
-   Ecore_Con_Socks_State ecs_state;
+   Ecore_Con_Proxy_State ecs_state;
    int ecs_addrlen;
    unsigned char ecs_addr[16];
    unsigned int ecs_buf_offset;
@@ -239,24 +244,14 @@ struct _Ecore_Con_Lookup
      v5 = (Ecore_Con_Socks_v5*)(X); \
    else
 
-struct Ecore_Con_Socks
-{
-   unsigned char version;
-
-   const char *ip;
-   int port;
-   const char *username;
-   Eina_Bool lookup : 1;
-   Eina_Bool bind : 1;
-};
-
-struct Ecore_Con_Socks_v4
+struct Ecore_Con_Socks /* v4 */
 {
    unsigned char version;
 
    const char *ip;
    int port;
    const char *username;
+   unsigned int ulen;
    Eina_Bool lookup : 1;
    Eina_Bool bind : 1;
 };
@@ -268,8 +263,13 @@ struct Ecore_Con_Socks_v5
    const char *ip;
    int port;
    const char *username;
+   unsigned int ulen;
    Eina_Bool lookup : 1;
    Eina_Bool bind : 1;
+   /* v5 only */
+   unsigned char method;
+   const char *password;
+   unsigned int plen;
 };
 
 extern Ecore_Con_Socks *_ecore_con_proxy_once;
index aecaff0..9b3888d 100644 (file)
 #include "Ecore_Con.h"
 #include "ecore_con_private.h"
 
+/* http://tools.ietf.org/html/rfc1928
+          o  X'00' NO AUTHENTICATION REQUIRED
+          o  X'01' GSSAPI
+          o  X'02' USERNAME/PASSWORD
+          o  X'03' to X'7F' IANA ASSIGNED
+          o  X'80' to X'FE' RESERVED FOR PRIVATE METHODS
+          o  X'FF' NO ACCEPTABLE METHODS
+*/
+#define ECORE_CON_SOCKS_V5_METHOD_NONE 0
+#define ECORE_CON_SOCKS_V5_METHOD_GSSAPI 1
+#define ECORE_CON_SOCKS_V5_METHOD_USERPASS 2
+
+static int ECORE_CON_SOCKS_V5_METHODS[] =
+{
+   ECORE_CON_SOCKS_V5_METHOD_NONE,
+//   ECORE_CON_SOCKS_V5_METHOD_GSSAPI, TODO
+   ECORE_CON_SOCKS_V5_METHOD_USERPASS
+};
+
+#define ECORE_CON_SOCKS_V5_TOTAL_METHODS sizeof(ECORE_CON_SOCKS_V5_METHODS)
+
 #define _ecore_con_server_kill(svr) do { \
    DBG("KILL %p", (svr)); \
    _ecore_con_server_kill((svr)); \
 Eina_List *ecore_con_socks_proxies = NULL;
 
 static Ecore_Con_Socks *
-_ecore_con_socks_find(unsigned char version, const char *ip, int port, const char *username)
+_ecore_con_socks_find(unsigned char version, const char *ip, int port, const char *username, size_t ulen, const char *password, size_t plen)
 {
    Eina_List *l;
-   Ecore_Con_Socks *ecs;
+   Ecore_Con_Socks_v5 *ecs;
 
    if (!ecore_con_socks_proxies) return NULL;
 
@@ -77,8 +98,14 @@ _ecore_con_socks_find(unsigned char version, const char *ip, int port, const cha
         if (ecs->version != version) continue;
         if (strcmp(ecs->ip, ip)) continue;
         if ((port != -1) && (port != ecs->port)) continue;
+        if (ulen != ecs->ulen) continue;
         if (username && strcmp(ecs->username, username)) continue;
-        return ecs;
+        if (version == 5)
+          {
+             if (plen != ecs->plen) continue;
+             if (password && strcmp(ecs->password, password)) continue;
+          }
+        return (Ecore_Con_Socks*)ecs;
      }
    return NULL;
 }
@@ -94,146 +121,424 @@ _ecore_con_socks_free(Ecore_Con_Socks *ecs)
    eina_stringshare_del(ecs->username);
    free(ecs);
 }
-/////////////////////////////////////////////////////////////////////////////////////
-void
-ecore_con_socks_shutdown(void)
+
+static Eina_Bool
+_ecore_con_socks_svr_init_v4(Ecore_Con_Server *svr, Ecore_Con_Socks_v4 *v4)
 {
-   Ecore_Con_Socks *ecs;
-   EINA_LIST_FREE(ecore_con_socks_proxies, ecs)
-     _ecore_con_socks_free(ecs);
-   _ecore_con_proxy_once = NULL;
-   _ecore_con_proxy_global = NULL;
+   size_t addrlen, buflen, ulen = 1;
+   unsigned char *sbuf;
+
+   addrlen = v4->lookup ? strlen(svr->name) + 1 : 0;
+   if (v4->username) ulen += v4->ulen;
+   buflen = sizeof(char) * (8  + ulen + addrlen);
+   sbuf = malloc(buflen);
+   if (!sbuf)
+     {
+        ecore_con_event_server_error(svr, "Memory allocation failure!");
+        _ecore_con_server_kill(svr);
+        return EINA_FALSE;
+     }
+   /* http://en.wikipedia.org/wiki/SOCKS */
+   sbuf[0] = 4;
+   sbuf[1] = v4->bind ? 2 : 1;
+   sbuf[2] = svr->port >> 8;
+   sbuf[3] = svr->port & 0xff;
+   if (addrlen)
+     {
+        sbuf[4] = sbuf[5] = sbuf[6] = 0;
+        sbuf[7] = 1;
+     }
+   else
+     /* SOCKSv4 only handles IPV4, so addrlen is always 4 */
+     memcpy(sbuf + 4, svr->ecs_addr, 4);
+   if (v4->username)
+     memcpy(sbuf + 8, v4->username, ulen);
+   else
+     sbuf[8] = 0;
+   if (addrlen) memcpy(sbuf + 8 + ulen, svr->name, addrlen);
+
+   svr->ecs_buf = eina_binbuf_manage_new_length(sbuf, buflen);
+   return EINA_TRUE;
 }
 
-void
-ecore_con_socks_read(Ecore_Con_Server *svr, unsigned char *buf, int num)
+static Eina_Bool
+_ecore_con_socks_svr_init_v5(Ecore_Con_Server *svr, Ecore_Con_Socks_v5 *v5)
+{
+   size_t buflen;
+   unsigned int x;
+   unsigned char *sbuf;
+
+   if (v5->username)
+     buflen = sizeof(char) * (2  + ECORE_CON_SOCKS_V5_TOTAL_METHODS);
+   else
+     buflen = 3;
+   sbuf = malloc(buflen);
+   if (!sbuf)
+     {
+        ecore_con_event_server_error(svr, "Memory allocation failure!");
+        _ecore_con_server_kill(svr);
+        return EINA_FALSE;
+     }
+   /* http://en.wikipedia.org/wiki/SOCKS
+    * http://tools.ietf.org/html/rfc1928
+    */
+   sbuf[0] = 5;
+   if (v5->username)
+     {
+       sbuf[1] = ECORE_CON_SOCKS_V5_TOTAL_METHODS;
+       for (x = 2; x < 2 + ECORE_CON_SOCKS_V5_TOTAL_METHODS; x++)
+         sbuf[x] = ECORE_CON_SOCKS_V5_METHODS[x - 2];
+     }
+   else
+     {
+        sbuf[1] = 1;
+        sbuf[2] = ECORE_CON_SOCKS_V5_METHOD_NONE;
+     }
+
+   svr->ecs_buf = eina_binbuf_manage_new_length(sbuf, buflen);
+   return EINA_TRUE;
+}
+
+#define ECORE_CON_SOCKS_READ(EXACT) \
+          if (num < EXACT) \
+            { \
+               if (!svr->ecs_recvbuf) svr->ecs_recvbuf = eina_binbuf_new(); \
+               if (!svr->ecs_recvbuf) goto error; \
+               eina_binbuf_append_length(svr->ecs_recvbuf, buf, num); \
+               /* the slowest connection on earth */ \
+               if (eina_binbuf_length_get(svr->ecs_recvbuf) != EXACT) return; \
+               data = eina_binbuf_string_get(svr->ecs_recvbuf); \
+            } \
+          else if (num > EXACT) goto error; \
+          else \
+            data = buf
+
+static void
+_ecore_con_socks_read_v4(Ecore_Con_Server *svr, Ecore_Con_Socks_v4 *v4 __UNUSED__, const unsigned char *buf, unsigned int num)
 {
    const unsigned char *data;
-   ECORE_CON_SOCKS_CAST_ELSE(svr->ecs) return;
+   DBG("SOCKS: %d bytes", num);
+   ECORE_CON_SOCKS_READ(8);
+
+/* http://ufasoft.com/doc/socks4_protocol.htm */
+   if (data[0]) goto error;
+   switch (data[1])
+     {
+      case 90:
+        /* success! */
+        break;
+      case 91:
+        ecore_con_event_server_error(svr, "proxy request rejected or failed");
+        goto error;
+      case 92:
+        ecore_con_event_server_error(svr, "proxying SOCKS server could not perform authentication");
+        goto error;
+      case 93:
+        ecore_con_event_server_error(svr, "proxy request authentication rejected");
+        goto error;
+      default:
+        ecore_con_event_server_error(svr, "garbage data from proxy");
+        goto error;
+     }
+   if (svr->ecs->bind)
+     {
+        unsigned int nport;
+        char naddr[IF_NAMESIZE];
+
+        memcpy(&nport, &data[2], 2);
+        svr->proxyport = ntohl(nport);
 
-   if (svr->ecs_state != ECORE_CON_SOCKS_STATE_READ) return;
+        if (!inet_ntop(AF_INET, &data[4], naddr, sizeof(naddr))) goto error;
+        svr->proxyip = eina_stringshare_add(naddr);
+        ecore_con_event_proxy_bind(svr);
+     }
+   svr->ecs_state = ECORE_CON_PROXY_STATE_DONE;
+   INF("PROXY CONNECTED");
+   if (svr->ecs_recvbuf) eina_binbuf_free(svr->ecs_recvbuf);
+   svr->ecs_recvbuf = NULL;
+   svr->ecs_buf_offset = svr->ecs_addrlen = 0;
+   memset(svr->ecs_addr, 0, sizeof(svr->ecs_addr));
+   if (!svr->ssl_state)
+     ecore_con_event_server_add(svr);
+   if (svr->ssl_state || (svr->buf && eina_binbuf_length_get(svr->buf)))
+     ecore_main_fd_handler_active_set(svr->fd_handler, ECORE_FD_READ | ECORE_FD_WRITE);
+   return;
+error:
+   _ecore_con_server_kill(svr);
+}
 
-   if (v4)
+static Eina_Bool
+_ecore_con_socks_auth_v5(Ecore_Con_Server *svr, Ecore_Con_Socks_v5 *v5)
+{
+   size_t size;
+   unsigned char *data;
+   switch (v5->method)
      {
-        DBG("SOCKS: %d bytes", num);
-        if (num < 8)
-          {
-             if (!svr->ecs_recvbuf) svr->ecs_recvbuf = eina_binbuf_new();
-             if (!svr->ecs_recvbuf) goto error;
-             eina_binbuf_append_length(svr->ecs_recvbuf, buf, num);
-             /* the slowest connection on earth */
-             if (eina_binbuf_length_get(svr->ecs_recvbuf) != 8) return;
-             data = eina_binbuf_string_get(svr->ecs_recvbuf);
-          }
-        else if (num > 8) goto error;
-        else
-          data = buf;
+        case ECORE_CON_SOCKS_V5_METHOD_NONE:
+          svr->ecs_state = ECORE_CON_PROXY_STATE_REQUEST;
+          return EINA_TRUE;
+        case ECORE_CON_SOCKS_V5_METHOD_GSSAPI:
+          return EINA_TRUE;
+        case ECORE_CON_SOCKS_V5_METHOD_USERPASS:
+          if (!v5->username) return EINA_FALSE;
+          if (!v5->password) v5->plen = 1;
+          /* http://tools.ietf.org/html/rfc1929 */
+          size = sizeof(char) * (3 + v5->ulen + v5->plen);
+          data = malloc(size);
+          if (!data) break;
+          data[0] = 1;
+          data[1] = v5->ulen;
+          memcpy(&data[2], v5->username, v5->ulen);
+          data[1 + v5->ulen] = v5->plen;
+          if (v5->password)
+            memcpy(&data[2 + v5->ulen], v5->password, v5->plen);
+          else
+            data[2 + v5->ulen] = 0;
+          svr->ecs_buf = eina_binbuf_manage_new_length(data, size);
+          return EINA_TRUE;
+        default:
+          break;
+     }
+   return EINA_FALSE;
+}
+
+static void
+_ecore_con_socks_read_v5(Ecore_Con_Server *svr, Ecore_Con_Socks_v5 *v5, const unsigned char *buf, unsigned int num)
+{
+   const unsigned char *data;
 
-     /* http://ufasoft.com/doc/socks4_protocol.htm */
-        if (data[0]) goto error;
-        switch (data[1])
+   DBG("SOCKS: %d bytes", num);
+   switch (svr->ecs_state)
+     {
+
+        case ECORE_CON_PROXY_STATE_READ:
+          ECORE_CON_SOCKS_READ(2);
+          /* http://en.wikipedia.org/wiki/SOCKS */
+          if (data[0] != 5) goto error;
+          if (data[1] == 0xFF)
+            {
+               ecore_con_event_server_error(svr, "proxy authentication methods rejected");
+               goto error;
+            }
+          v5->method = data[1];
+          if (!_ecore_con_socks_auth_v5(svr, v5)) goto error;
+          if (svr->ecs_state == ECORE_CON_PROXY_STATE_REQUEST)
+            {
+               /* run again to skip auth reading */
+               _ecore_con_socks_read_v5(svr, v5, NULL, 0);
+               return;
+            }
+          ecore_main_fd_handler_active_set(svr->fd_handler, ECORE_FD_WRITE);
+          svr->ecs_state = ECORE_CON_PROXY_STATE_AUTH;
+          break;
+        case ECORE_CON_PROXY_STATE_AUTH:
+          ECORE_CON_SOCKS_READ(2);
+          switch (v5->method)
+            {
+             case ECORE_CON_SOCKS_V5_METHOD_NONE:
+               CRIT("HOW DID THIS HAPPEN?????????");
+               goto error;
+             case ECORE_CON_SOCKS_V5_METHOD_GSSAPI:
+               /* TODO: this */
+               break;
+             case ECORE_CON_SOCKS_V5_METHOD_USERPASS:
+               if (data[0] != 1)
+                 {
+                    ecore_con_event_server_error(svr, "protocol error");
+                    goto error; /* wrong version */
+                 }
+               if (data[1])
+                 {
+                    ecore_con_event_server_error(svr, "proxy request authentication rejected");
+                    goto error;
+                 }
+             default:
+               break;
+            }
+        case ECORE_CON_PROXY_STATE_REQUEST:
           {
-           case 90:
-             /* success! */
-             break;
-           case 91:
-             ecore_con_event_server_error(svr, "proxy request rejected or failed");
-             goto error;
-           case 92:
-             ecore_con_event_server_error(svr, "proxying SOCKS server could not perform authentication");
-             goto error;
-           case 93:
-             ecore_con_event_server_error(svr, "proxy request authentication rejected");
-             goto error;
-           default:
-             ecore_con_event_server_error(svr, "garbage data from proxy");
-             goto error;
+             size_t addrlen, buflen;
+             unsigned char *sbuf;
+             addrlen = v5->lookup ? strlen(svr->name) + 1 : (unsigned int)svr->ecs_addrlen;
+             buflen = sizeof(char) * (6 + addrlen);
+             sbuf = malloc(buflen);
+             if (!sbuf)
+               {
+                  ecore_con_event_server_error(svr, "Memory allocation failure!");
+                  goto error;
+               }
+              sbuf[0] = 5;
+              sbuf[1] = v5->bind ? 2 : 1; /* TODO: 0x03 for UDP port association */
+              sbuf[2] = 0;
+              if (v5->lookup) /* domain name */
+                {
+                   sbuf[3] = 3;
+                   sbuf[4] = addrlen - 1;
+                   memcpy(sbuf + 5, svr->name, addrlen - 1);
+                }
+              else
+                {
+                   sbuf[3] = (svr->ecs_addrlen == 4) ? 1 : 4;
+                   memcpy(sbuf + 4, svr->ecs_addr, addrlen);
+                }
+              sbuf[addrlen + 4] = svr->port >> 8;
+              sbuf[addrlen + 5] = svr->port & 0xff;
+
+              svr->ecs_buf = eina_binbuf_manage_new_length(sbuf, buflen);
+              ecore_main_fd_handler_active_set(svr->fd_handler, ECORE_FD_WRITE);
+              break;
           }
-        if (svr->ecs->bind)
+        case ECORE_CON_PROXY_STATE_CONFIRM:
           {
-             unsigned int nport;
-             char naddr[IF_NAMESIZE];
-
-             memcpy(&nport, &data[2], 2);
-             svr->proxyport = ntohl(nport);
-
-             if (!inet_ntop(AF_INET, &data[4], naddr, sizeof(naddr))) goto error;
-             svr->proxyip = eina_stringshare_add(naddr);
-             ecore_con_event_proxy_bind(svr);
+             /* this is ugly because we have to read an exact number of bytes,
+              * but we don't know what that number is until we've already read
+              * at least 5 bytes to determine the length of the unknown stream.
+              * yep.
+              */
+             size_t to_read, len = svr->ecs_recvbuf ? eina_binbuf_length_get(svr->ecs_recvbuf) : 0;
+             if (num + len < 5)
+               {
+                  /* guarantees we get called again */
+                  ECORE_CON_SOCKS_READ(5);
+               }
+             if (len >= 5)
+               {
+                  data = eina_binbuf_string_get(svr->ecs_recvbuf);
+                  data += 3;
+               }
+             else
+               data = buf + 3 - len;
+             switch (data[0])
+               {
+                  case 1:
+                    to_read = 4;
+                    break;
+                  case 3:
+                    to_read = data[1] + 1;
+                    break;
+                  case 4:
+                    to_read = 16;
+                    /* lazy debugging stub comment */
+                    break;
+                  default:
+                    ecore_con_event_server_error(svr, "protocol error");
+                    goto error;
+               }
+             /* at this point, we finally know exactly how much we need to read */
+             ECORE_CON_SOCKS_READ(6 + to_read);
+
+             if (data[0] != 5)
+               {
+                  ecore_con_event_server_error(svr, "protocol error");
+                  goto error; /* wrong version */
+               }
+             switch (data[1])
+               {
+                  case 0:
+                    break;
+                  case 1:
+                    ecore_con_event_server_error(svr, "general proxy failure");
+                    goto error;
+                  case 2:
+                    ecore_con_event_server_error(svr, "connection not allowed by ruleset");
+                    goto error;
+                  case 3:
+                    ecore_con_event_server_error(svr, "network unreachable");
+                    goto error;
+                  case 4:
+                    ecore_con_event_server_error(svr, "host unreachable");
+                    goto error;
+                  case 5:
+                    ecore_con_event_server_error(svr, "connection refused by destination host");
+                    goto error;
+                  case 6:
+                    ecore_con_event_server_error(svr, "TTL expired");
+                    goto error;
+                  case 7:
+                    ecore_con_event_server_error(svr, "command not supported / protocol error");
+                    goto error;
+                  case 8:
+                    ecore_con_event_server_error(svr, "address type not supported");
+                  default:
+                    goto error;
+               }
+             if (data[2])
+               {
+                  ecore_con_event_server_error(svr, "protocol error");
+                  goto error;
+               }
+             memset(svr->ecs_addr, 0, sizeof(svr->ecs_addr));
+             if (!svr->ssl_state)
+               ecore_con_event_server_add(svr);
+             if (svr->ssl_state || (svr->buf && eina_binbuf_length_get(svr->buf)))
+               ecore_main_fd_handler_active_set(svr->fd_handler, ECORE_FD_READ | ECORE_FD_WRITE);
+             svr->ecs_buf_offset = svr->ecs_addrlen = 0;
+             svr->ecs_state = ECORE_CON_PROXY_STATE_DONE;
+             INF("PROXY CONNECTED");
+             break;
           }
-        svr->ecs_state = ECORE_CON_SOCKS_STATE_DONE;
-        INF("PROXY CONNECTED");
-        if (svr->ecs_recvbuf) eina_binbuf_free(svr->ecs_recvbuf);
-        svr->ecs_recvbuf = NULL;
-        svr->ecs_buf_offset = svr->ecs_addrlen = 0;
-        memset(svr->ecs_addr, 0, sizeof(svr->ecs_addr));
-        if (!svr->ssl_state)
-          ecore_con_event_server_add(svr);
-        if (svr->ssl_state || (svr->buf && eina_binbuf_length_get(svr->buf)))
-          ecore_main_fd_handler_active_set(svr->fd_handler, ECORE_FD_READ | ECORE_FD_WRITE);
+      default:
+        break;
      }
+   if (svr->ecs_recvbuf) eina_binbuf_free(svr->ecs_recvbuf);
+   svr->ecs_recvbuf = NULL;
+   
    return;
 error:
    _ecore_con_server_kill(svr);
 }
 
+/////////////////////////////////////////////////////////////////////////////////////
+void
+ecore_con_socks_shutdown(void)
+{
+   Ecore_Con_Socks *ecs;
+   EINA_LIST_FREE(ecore_con_socks_proxies, ecs)
+     _ecore_con_socks_free(ecs);
+   _ecore_con_proxy_once = NULL;
+   _ecore_con_proxy_global = NULL;
+}
+
+void
+ecore_con_socks_read(Ecore_Con_Server *svr, unsigned char *buf, int num)
+{
+   ECORE_CON_SOCKS_CAST_ELSE(svr->ecs) return;
+
+   if (svr->ecs_state < ECORE_CON_PROXY_STATE_READ) return;
+
+   if (v4) _ecore_con_socks_read_v4(svr, v4, buf, (unsigned int)num);
+   else _ecore_con_socks_read_v5(svr, v5, buf, (unsigned int)num);
+}
+
 Eina_Bool
 ecore_con_socks_svr_init(Ecore_Con_Server *svr)
 {
-   unsigned char *sbuf;
    ECORE_CON_SOCKS_CAST_ELSE(svr->ecs) return EINA_FALSE;
 
    if (!svr->ip) return EINA_FALSE;
    if (svr->ecs_buf) return EINA_FALSE;
-   if (svr->ecs_state != ECORE_CON_SOCKS_STATE_INIT) return EINA_FALSE;
+   if (svr->ecs_state != ECORE_CON_PROXY_STATE_INIT) return EINA_FALSE;
    ecore_main_fd_handler_active_set(svr->fd_handler, ECORE_FD_WRITE);
-   if (v4)
-     {
-        size_t addrlen, buflen, ulen = 1;
-        addrlen = svr->ecs->lookup ? strlen(svr->name) + 1: 0;
-        if (svr->ecs->username) ulen += strlen(svr->ecs->username);
-        buflen = sizeof(char) * (8  + ulen + addrlen);
-        sbuf = malloc(buflen);
-        if (!sbuf)
-          {
-             ecore_con_event_server_error(svr, "Memory allocation failure!");
-             _ecore_con_server_kill(svr);
-             return EINA_FALSE;
-          }
-        /* http://en.wikipedia.org/wiki/SOCKS */
-        sbuf[0] = 4;
-        sbuf[1] = v4->bind ? 2 : 1;
-        sbuf[2] = svr->port >> 8;
-        sbuf[3] = svr->port & 0xff;
-        if (addrlen)
-          {
-             sbuf[4] = sbuf[5] = sbuf[6] = 0;
-             sbuf[7] = 1;
-          }
-        else
-          memcpy(sbuf + 4, svr->ecs_addr, 4);
-        if (svr->ecs->username)
-          memcpy(sbuf + 8, svr->ecs->username, ulen);
-        else
-          sbuf[8] = 0;
-        if (addrlen) memcpy(sbuf + 8 + ulen, svr->name, addrlen);
-
-        svr->ecs_buf = eina_binbuf_manage_new_length(sbuf, buflen);
-     }
-   return EINA_TRUE;
+   if (v4) return _ecore_con_socks_svr_init_v4(svr, v4);
+   return _ecore_con_socks_svr_init_v5(svr, v5);
 }
 
 void
-ecore_con_socks_dns_cb(const char *canonname __UNUSED__, const char *ip, struct sockaddr *addr, int addrlen, Ecore_Con_Server *svr)
+ecore_con_socks_dns_cb(const char *canonname __UNUSED__, const char *ip, struct sockaddr *addr, int addrlen __UNUSED__, Ecore_Con_Server *svr)
 {
    svr->ip = eina_stringshare_add(ip);
-   svr->ecs_addrlen = addrlen;
    svr->ecs_state++;
    if (addr->sa_family == AF_INET)
-     memcpy(svr->ecs_addr, &((struct sockaddr_in *)addr)->sin_addr.s_addr, 4);
+     {
+        memcpy(svr->ecs_addr, &((struct sockaddr_in *)addr)->sin_addr.s_addr, 4);
+        svr->ecs_addrlen = 4;
+     }
 #ifdef HAVE_IPV6
    else
-     memcpy(svr->ecs_addr, &((struct sockaddr_in6 *)addr)->sin6_addr.s6_addr, addrlen);
+     {
+        memcpy(svr->ecs_addr, &((struct sockaddr_in6 *)addr)->sin6_addr.s6_addr, 16);
+        svr->ecs_addrlen = 16;
+     }
 #endif
    ecore_con_socks_svr_init(svr);
 }
@@ -242,36 +547,57 @@ void
 ecore_con_socks_init(void)
 {
    const char *socks;
-   char *u, *h, *p, *l;
-   char buf[64];
+   char *h, *p, *l, *u = NULL;
+   char buf[512];
    int port, lookup = 0;
+   Eina_Bool v5 = EINA_FALSE;
    Ecore_Con_Socks *ecs;
    unsigned char addr[sizeof(struct in_addr)];
+#ifdef HAVE_IPV6
+   unsigned char addr6[sizeof(struct in6_addr)];
+#endif
 
-   /* ECORE_CON_SOCKS_V4=user@host:port:[1|0] */
+   /* ECORE_CON_SOCKS_V4=[user@]host:port-[1|0] */
    socks = getenv("ECORE_CON_SOCKS_V4");
-   if ((!socks) || (!socks[0]) || (strlen(socks) > 64)) return;
+   if (!socks)
+     {
+        /* ECORE_CON_SOCKS_V5=[user@]host-port:[1|0] */
+        socks = getenv("ECORE_CON_SOCKS_V5");
+        v5 = EINA_TRUE;
+     }
+   if ((!socks) || (!socks[0]) || (strlen(socks) > 512)) return;
    strncpy(buf, socks, sizeof(buf));
    h = strchr(buf, '@');
-   u = NULL;
    /* username */
    if (h && (h - buf > 0)) *h++ = 0, u = buf;
    else h = buf;
 
    /* host ip; I ain't resolvin shit here */
-   p = strchr(h, ':');
+   p = strchr(h, '-');
    if (!p) return;
    *p++ = 0;
-   if (!inet_pton(AF_INET, h, addr)) return;
+   if (!inet_pton(AF_INET, h, addr))
+#ifdef HAVE_IPV6
+     {
+        if (!v5) return;
+        if (!inet_pton(AF_INET6, h, addr6))
+          return;
+     }
+#else
+     return;
+#endif
 
    errno = 0;
    port = strtol(p, &l, 10);
    if (errno || (port < 0) || (port > 65535)) return;
    if (l && (l[0] == ':'))
      lookup = (l[1] == '1');
-   ecs = ecore_con_socks4_remote_add(h, port, u);
+   if (v5)
+     ecs = ecore_con_socks5_remote_add(h, port, u, NULL);
+   else
+     ecs = ecore_con_socks4_remote_add(h, port, u);
    if (!ecs) return;
-   ecore_con_socks4_lookup_set(ecs, lookup);
+   ecore_con_socks_lookup_set(ecs, lookup);
    ecore_con_socks_apply_always(ecs);
    INF("Added global proxy server %s%s%s:%d - DNS lookup %s",
        u ?: "", u ? "@" : "", h, port, lookup ? "ENABLED" : "DISABLED");
@@ -300,10 +626,17 @@ EAPI Ecore_Con_Socks *
 ecore_con_socks4_remote_add(const char *ip, int port, const char *username)
 {
    Ecore_Con_Socks *ecs;
+   size_t ulen = 0;
 
    if ((!ip) || (!ip[0]) || (port < 0) || (port > 65535)) return NULL;
 
-   ecs = _ecore_con_socks_find(4, ip, port, username);
+   if (username)
+     {
+        ulen = strlen(username);
+        /* max length for protocol */
+        if ((!ulen) || (ulen > 255)) return NULL;
+     }
+   ecs = _ecore_con_socks_find(4, ip, port, username, ulen, NULL, 0);
    if (ecs) return ecs;
 
    ecs = calloc(1, sizeof(Ecore_Con_Socks_v4));
@@ -313,70 +646,131 @@ ecore_con_socks4_remote_add(const char *ip, int port, const char *username)
    ecs->ip = eina_stringshare_add(ip);
    ecs->port = port;
    ecs->username = eina_stringshare_add(username);
+   ecs->ulen = ulen;
    ecore_con_socks_proxies = eina_list_append(ecore_con_socks_proxies, ecs);
    return ecs;
 }
 
 /**
- * Set DNS lookup mode on an existing SOCKS v4 proxy
+ * Find a SOCKS v4 proxy in the proxy list
  *
- * According to RFC, SOCKS v4 does not require that a proxy perform
- * its own DNS lookups for addresses. SOCKS v4a specifies the protocol
- * for this. If you want to enable remote DNS lookup and are sure that your
- * proxy supports it, use this function.
- * @param ecs The proxy object
- * @param enable If true, the proxy will perform the dns lookup
- * @note By default, this setting is DISABLED.
+ * Use this to determine if a SOCKS proxy was previously added by checking
+ * the proxy list against the parameters given.
+ * @param ip The ip address of the proxy (NOT DOMAIN NAME. IP ADDRESS.)
+ * @param port The port to connect to on the proxy, or -1 to match the first proxy with @p ip
+ * @param username The username used for the proxy (OPTIONAL)
+ * @return true only if a proxy exists matching the given params
+ * @note This function matches slightly more loosely than ecore_con_socks4_remote_add(), and
+ * ecore_con_socks4_remote_add() should be used to return the actual object.
  * @since 1.2
  */
-EAPI void
-ecore_con_socks4_lookup_set(Ecore_Con_Socks *ecs, Eina_Bool enable)
+EAPI Eina_Bool
+ecore_con_socks4_remote_exists(const char *ip, int port, const char *username)
 {
-   ECORE_CON_SOCKS_CAST_ELSE(ecs) return;
-   if (v4) v4->lookup = !!enable;
+   if ((!ip) || (!ip[0]) || (port < -1) || (port > 65535) || (username && (!username[0])))
+     return EINA_FALSE;
+   return !!_ecore_con_socks_find(4, ip, port, username, username ? strlen(username) : 0, NULL, 0);
 }
 
 /**
- * Get DNS lookup mode on an existing SOCKS v4 proxy
+ * Remove a SOCKS v4 proxy from the proxy list and delete it
  *
- * According to RFC, SOCKS v4 does not require that a proxy perform
- * its own DNS lookups for addresses. SOCKS v4a specifies the protocol
- * for this. This function returns whether lookups are enabled on a proxy object.
- * @param ecs The proxy object
- * @return If true, the proxy will perform the dns lookup
- * @note By default, this setting is DISABLED.
+ * Use this to remove a SOCKS proxy from the proxy list by checking
+ * the list against the parameters given. The proxy will then be deleted.
+ * @param ip The ip address of the proxy (NOT DOMAIN NAME. IP ADDRESS.)
+ * @param port The port to connect to on the proxy, or -1 to match the first proxy with @p ip
+ * @param username The username used for the proxy (OPTIONAL)
+ * @note This function matches in the same way as ecore_con_socks4_remote_exists().
+ * @warning Be aware that deleting a proxy which is being used WILL ruin your life.
  * @since 1.2
  */
-EAPI Eina_Bool
-ecore_con_socks4_lookup_get(Ecore_Con_Socks *ecs)
+EAPI void
+ecore_con_socks4_remote_del(const char *ip, int port, const char *username)
 {
-   ECORE_CON_SOCKS_CAST_ELSE(ecs) return EINA_FALSE;
-   return v4 ? v4->lookup : EINA_FALSE;
+   Ecore_Con_Socks_v4 *v4;
+
+   if ((!ip) || (!ip[0]) || (port < -1) || (port > 65535) || (username && (!username[0]))) return;
+   if (!ecore_con_socks_proxies) return;
+
+   v4 = (Ecore_Con_Socks_v4*)_ecore_con_socks_find(4, ip, port, username, username ? strlen(username) : 0, NULL, 0);
+   if (!v4) return;
+   ecore_con_socks_proxies = eina_list_remove(ecore_con_socks_proxies, v4);
+   _ecore_con_socks_free((Ecore_Con_Socks*)v4);
+}
+/**
+ * Add a SOCKS v5 proxy to the proxy list
+ *
+ * Use this to create (or return, if previously added) a SOCKS proxy
+ * object which can be used by any ecore_con servers.
+ * @param ip The ip address of the proxy (NOT DOMAIN NAME. IP ADDRESS.)
+ * @param port The port to connect to on the proxy
+ * @param username The username to use for the proxy (OPTIONAL)
+ * @param password The password to use for the proxy (OPTIONAL)
+ * @return An allocated proxy object, or NULL on failure
+ * @note This object NEVER needs to be explicitly freed.
+ * @since 1.2
+ */
+EAPI Ecore_Con_Socks *
+ecore_con_socks5_remote_add(const char *ip, int port, const char *username, const char *password)
+{
+   Ecore_Con_Socks_v5 *ecs5;
+   size_t ulen = 0, plen = 0;
+
+   if ((!ip) || (!ip[0]) || (port < 0) || (port > 65535)) return NULL;
+
+   if (username)
+     {
+        ulen = strlen(username);
+        /* max length for protocol */
+        if ((!ulen) || (ulen > 255)) return NULL;
+     }
+   if (password)
+     {
+        plen = strlen(password);
+        /* max length for protocol */
+        if ((!plen) || (plen > 255)) return NULL;
+     }
+   ecs5 = (Ecore_Con_Socks_v5*)_ecore_con_socks_find(5, ip, port, username, ulen, password, plen);
+   if (ecs5) return (Ecore_Con_Socks*)ecs5;
+
+   ecs5 = calloc(1, sizeof(Ecore_Con_Socks_v5));
+   if (!ecs5) return NULL;
+
+   ecs5->version = 5;
+   ecs5->ip = eina_stringshare_add(ip);
+   ecs5->port = port;
+   ecs5->username = eina_stringshare_add(username);
+   ecs5->ulen = ulen;
+   ecs5->password = eina_stringshare_add(password);
+   ecs5->plen = plen;
+   ecore_con_socks_proxies = eina_list_append(ecore_con_socks_proxies, ecs5);
+   return (Ecore_Con_Socks*)ecs5;
 }
 
 /**
- * Find a SOCKS v4 proxy in the proxy list
+ * Find a SOCKS v5 proxy in the proxy list
  *
  * Use this to determine if a SOCKS proxy was previously added by checking
  * the proxy list against the parameters given.
  * @param ip The ip address of the proxy (NOT DOMAIN NAME. IP ADDRESS.)
  * @param port The port to connect to on the proxy, or -1 to match the first proxy with @p ip
  * @param username The username used for the proxy (OPTIONAL)
+ * @param password The password used for the proxy (OPTIONAL)
  * @return true only if a proxy exists matching the given params
- * @note This function matches slightly more loosely than ecore_con_socks4_remote_add(), and
- * ecore_con_socks4_remote_add() should be used to return the actual object.
+ * @note This function matches slightly more loosely than ecore_con_socks5_remote_add(), and
+ * ecore_con_socks5_remote_add() should be used to return the actual object.
  * @since 1.2
  */
 EAPI Eina_Bool
-ecore_con_socks4_remote_exists(const char *ip, int port, const char *username)
+ecore_con_socks5_remote_exists(const char *ip, int port, const char *username, const char *password)
 {
-   if ((!ip) || (!ip[0]) || (port < -1) || (port > 65535) || (username && (!username[0])))
+   if ((!ip) || (!ip[0]) || (port < -1) || (port > 65535) || (username && (!username[0])) || (password && (!password[0])))
      return EINA_FALSE;
-   return !!_ecore_con_socks_find(4, ip, port, username);
+   return !!_ecore_con_socks_find(5, ip, port, username, username ? strlen(username) : 0, password, strlen(password));
 }
 
 /**
- * Remove a SOCKS v4 proxy from the proxy list and delete it
+ * Remove a SOCKS v5 proxy from the proxy list and delete it
  *
  * Use this to remove a SOCKS proxy from the proxy list by checking
  * the list against the parameters given. The proxy will then be deleted.
@@ -388,17 +782,57 @@ ecore_con_socks4_remote_exists(const char *ip, int port, const char *username)
  * @since 1.2
  */
 EAPI void
-ecore_con_socks4_remote_del(const char *ip, int port, const char *username)
+ecore_con_socks5_remote_del(const char *ip, int port, const char *username, const char *password)
 {
-   Ecore_Con_Socks_v4 *v4;
+   Ecore_Con_Socks_v5 *v5;
 
-   if ((!ip) || (!ip[0]) || (port < -1) || (port > 65535) || (username && (!username[0]))) return;
+   if ((!ip) || (!ip[0]) || (port < -1) || (port > 65535) || (username && (!username[0])) || (password && (!password[0])))
+     return;
    if (!ecore_con_socks_proxies) return;
 
-   v4 = (Ecore_Con_Socks_v4*)_ecore_con_socks_find(4, ip, port, username);
-   if (!v4) return;
-   ecore_con_socks_proxies = eina_list_remove(ecore_con_socks_proxies, v4);
-   _ecore_con_socks_free((Ecore_Con_Socks*)v4);
+   v5 = (Ecore_Con_Socks_v5*)_ecore_con_socks_find(5, ip, port, username, username ? strlen(username) : 0, password, strlen(password));
+   if (!v5) return;
+   ecore_con_socks_proxies = eina_list_remove(ecore_con_socks_proxies, v5);
+   _ecore_con_socks_free((Ecore_Con_Socks*)v5);
+}
+
+/**
+ * Set DNS lookup mode on an existing SOCKS proxy
+ *
+ * According to RFC, SOCKS v4 does not require that a proxy perform
+ * its own DNS lookups for addresses. SOCKS v4a specifies the protocol
+ * for this. SOCKS v5 allows DNS lookups.
+ * If you want to enable remote DNS lookup and are sure that your
+ * proxy supports it, use this function.
+ * @param ecs The proxy object
+ * @param enable If true, the proxy will perform the dns lookup
+ * @note By default, this setting is DISABLED.
+ * @since 1.2
+ */
+EAPI void
+ecore_con_socks_lookup_set(Ecore_Con_Socks *ecs, Eina_Bool enable)
+{
+   ECORE_CON_SOCKS_CAST_ELSE(ecs) return;
+   ecs->lookup = !!enable;
+}
+
+/**
+ * Get DNS lookup mode on an existing SOCKS proxy
+ *
+ * According to RFC, SOCKS v4 does not require that a proxy perform
+ * its own DNS lookups for addresses. SOCKS v4a specifies the protocol
+ * for this. SOCKS v5 allows DNS lookups.
+ * This function returns whether lookups are enabled on a proxy object.
+ * @param ecs The proxy object
+ * @return If true, the proxy will perform the dns lookup
+ * @note By default, this setting is DISABLED.
+ * @since 1.2
+ */
+EAPI Eina_Bool
+ecore_con_socks_lookup_get(Ecore_Con_Socks *ecs)
+{
+   ECORE_CON_SOCKS_CAST_ELSE(ecs) return EINA_FALSE;
+   return ecs->lookup;
 }
 
 /**
@@ -434,6 +868,14 @@ ecore_con_socks_bind_get(Ecore_Con_Socks *ecs)
    return ecs->bind;
 }
 
+/**
+ * Return SOCKS version of a SOCKS proxy
+ *
+ * Use this function to return the SOCKS protocol version of a proxy
+ * @param ecs The proxy object
+ * @return 0 on error, else 4/5
+ * @since 1.2
+ */
 EAPI unsigned int
 ecore_con_socks_version_get(Ecore_Con_Socks *ecs)
 {
@@ -482,7 +924,8 @@ ecore_con_socks_apply_once(Ecore_Con_Socks *ecs)
  * @see ecore_con_socks_apply_once()
  * @since 1.2
  * @note ecore-con supports setting this through environment variables like so:
- *   ECORE_CON_SOCKS_V4=[user@]server:port:lookup
+ *   ECORE_CON_SOCKS_V4=[user@]server-port:lookup
+ *   ECORE_CON_SOCKS_V5=[user@]server-port:lookup
  * user is the OPTIONAL string that would be passed to the proxy as the username
  * server is the IP_ADDRESS of the proxy server
  * port is the port to connect to on the proxy server