[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [msmtp-users] Proxy support patch



Hi!

On Mon, 13 Oct 2014 23:43:05 +0000, CustaiCo wrote:
> On Mon, Oct 13, 2014 at 10:29:13PM +0200, Martin Lambers wrote:
> > Hi!
> > 
> > I don't use a proxy myself; I have a few questions about SOCKS
> > support in general and your patch in particular:
> > 
> > 1. Is there any need for anything except SOCKS5? It has been around
> > for ages, does anybody really still need SOCKS4?
> 
> AFAIK, SOCKS4 is totally dead. Some older systems still do SOCKS4a,
> but it's not that common. It was already implemented so I kept it.

Then let's start with SOCKS5 only, and add other variants only if
required.

> > 2. Nobody protects the SOCKS5 protocol with TLS, right?
> 
> I'm sure there exists *some* person who does this, but it's not common
> in the slightest.

Good, then let's start with the simple solution here, too.

> > 3. Is there a valid use case for SOCKS5 authentication? It only
> > supports unprotected user/password transmission (well, and GSSAPI,
> > but nobody uses that). This makes it pretty useless.
> 
> It's really not any more work to do the basic user/password
> authentication than it is to do the normal protocol. It just adds one
> extra step.

That's right. Still, msmtp currently refuses to send authentication
data in plaintext over the net unless you force it, and I hesitate to
add such functionality for proxies. In this day and age, if you add
network-based authentication methods, they absolutely have to be
secure.

I propose the attached patch, which currently uses "localhost:1080" as
hardcoded proxy (this can be changed later). I tested it against  'ssh
-D 1080 -N mys-ssh-server'.

It is similar in functionality to your patch, but
- only implements SOCKS5 without authentication
- improves error diagnostics for the proxy connection

What do you think?

Martin
diff --git a/src/net.c b/src/net.c
index 925af62..421a1c5 100644
--- a/src/net.c
+++ b/src/net.c
@@ -3,7 +3,7 @@
  *
  * This file is part of msmtp, an SMTP client.
  *
- * Copyright (C) 2000, 2003, 2004, 2005, 2006, 2007, 2008, 2012
+ * Copyright (C) 2000, 2003, 2004, 2005, 2006, 2007, 2008, 2012, 2014
  * Martin Lambers <marlam@...23...>
  *
  *   This program is free software; you can redistribute it and/or modify
@@ -168,6 +168,95 @@ const char *wsa_strerror(int error_code)
 
 
 /*
+ * Wrapper function for recv() that sets an error string on failure.
+ */
+
+int net_recv(int fd, void *buf, size_t len, char **errstr)
+{
+    int r = recv(fd, buf, len, 0);
+    if (r < 0)
+    {
+#ifdef W32_NATIVE
+        int e = WSAGetLastError();
+        if (e == WSAEWOULDBLOCK)
+        {
+            *errstr = xasprintf(_("network read error: %s"),
+                    _("the operation timed out"));
+        }
+        else
+        {
+            *errstr = xasprintf(_("network read error: %s"),
+                    wsa_strerror(e));
+        }
+#else /* !W32_NATIVE */
+        if (errno == EINTR)
+        {
+            *errstr = xasprintf(_("operation aborted"));
+        }
+        else if (errno == EAGAIN)
+        {
+            *errstr = xasprintf(_("network read error: %s"),
+                    _("the operation timed out"));
+        }
+        else
+        {
+            *errstr = xasprintf(_("network read error: %s"),
+                    strerror(errno));
+        }
+#endif
+        return -1;
+    }
+    return r;
+}
+
+
+/*
+ * Wrapper function for send() that sets an error string on failure.
+ */
+
+int net_send(int fd, const void *buf, size_t len, char **errstr)
+{
+#ifdef W32_NATIVE
+    int ret;
+#else /* !W32_NATIVE */
+    ssize_t ret;
+#endif
+    if ((ret = send(fd, buf, len, 0)) < 0)
+    {
+#ifdef W32_NATIVE
+        int e = WSAGetLastError();
+        if (e == WSAEWOULDBLOCK)
+        {
+            *errstr = xasprintf(_("network write error: %s"),
+                    _("the operation timed out"));
+        }
+        else
+        {
+            *errstr = xasprintf(_("network write error: %s"),
+                    wsa_strerror(e));
+        }
+#else /* !W32_NATIVE */
+        if (errno == EINTR)
+        {
+            *errstr = xasprintf(_("operation aborted"));
+        }
+        else if (errno == EAGAIN)
+        {
+            *errstr = xasprintf(_("network write error: %s"),
+                    _("the operation timed out"));
+        }
+        else
+        {
+            *errstr = xasprintf(_("network write error: %s"),
+                    strerror(errno));
+        }
+#endif
+    }
+    return ret;
+}
+
+
+/*
  * net_lib_init()
  *
  * see net.h
@@ -341,8 +430,150 @@ void net_set_io_timeout(int socket, int seconds)
  * see net.h
  */
 
-int net_open_socket(const char *hostname, int port, int timeout, int *ret_fd,
-        char **canonical_name, char **address, char **errstr)
+int net_socks5_connect(int fd, const char *hostname, int port, char **errstr)
+{
+    /* maximum size of a SOCKS5 message (send or receive) */
+    const size_t required_buffer_size = 1 + 1 + 1 + 1 + 1 + 255 + 2;
+    unsigned char buffer[required_buffer_size];
+    size_t hostname_len = strlen(hostname);
+    unsigned short nport = htons(port);
+    size_t len;
+    int ret;
+
+    if (hostname_len > 0xff)
+    {
+        /* this hostname cannot be sent in a SOCKS5 message */
+        *errstr = xasprintf(_("proxy failure: %s"), _("host name too long"));
+        return NET_EPROXY;
+    }
+
+    /* Send greeting */
+    buffer[0] = 0x05;   /* SOCKS5 */
+    buffer[1] = 0x01;   /* one auth method supported: */
+    buffer[2] = 0x00;   /* no authentication */
+    if ((ret = net_send(fd, buffer, 3, errstr)) < 0)
+    {
+        return NET_EIO;
+    }
+    else if (ret < 3)
+    {
+        *errstr = xasprintf(_("network write error"));
+        return NET_EIO;
+    }
+    /* Receive greeting */
+    if ((ret = net_recv(fd, buffer, 2, errstr)) < 0)
+    {
+        return NET_EIO;
+    }
+    else if (ret < 2)
+    {
+        *errstr = xasprintf(_("network read error"));
+        return NET_EIO;
+    }
+    if (buffer[0] != 0x05               /* SOCKS5 */
+            || buffer[1] != 0x00)       /* no authentication */
+    {
+        *errstr = xasprintf(_("proxy failure: %s"), _("unexpected reply"));
+        return NET_EPROXY;
+    }
+    /* Send CONNECT request */
+    buffer[0] = 0x05;   /* SOCKS5 */
+    buffer[1] = 0x01;   /* CONNECT */
+    buffer[2] = 0x00;   /* reserved */
+    buffer[3] = 0x03;   /* Domain name follows */
+    buffer[4] = hostname_len;
+    memcpy(buffer + 5, hostname, hostname_len);
+    memcpy(buffer + 5 + hostname_len, &nport, 2);
+    len = 5 + hostname_len + 2;
+    if ((ret = net_send(fd, buffer, len, errstr)) < 0)
+    {
+        return NET_EIO;
+    }
+    else if ((size_t)ret < len)
+    {
+        *errstr = xasprintf(_("network write error"));
+        return NET_EIO;
+    }
+    /* Receive answer */
+    if ((ret = net_recv(fd, buffer, 5, errstr)) < 0)
+    {
+        return NET_EIO;
+    }
+    else if (ret < 5)
+    {
+        *errstr = xasprintf(_("network read error"));
+        return NET_EIO;
+    }
+    if (buffer[0] != 0x05               /* SOCKS5 */
+            || buffer[2] != 0x00        /* reserved */
+            || (buffer[3] != 0x01 && buffer[3] != 0x03 && buffer[3] != 0x04))
+    {
+        *errstr = xasprintf(_("proxy failure: %s"), _("unexpected reply"));
+        return NET_EPROXY;
+    }
+    if (buffer[3] == 0x01)
+    {
+        len = 4 - 1;    /* IPv4 */
+    }
+    else if (buffer[3] == 0x04)
+    {
+        len = 16 - 1;   /* IPv6 */
+    }
+    else /* Domain name */
+    {
+        len = buffer[4];
+    }
+    len += 2;   /* port number */
+    if ((ret = net_recv(fd, buffer, len, errstr)) < 0)
+    {
+        return NET_EIO;
+    }
+    else if ((size_t)ret < len)
+    {
+        *errstr = xasprintf(_("network read error"));
+        return NET_EIO;
+    }
+    /* Interpret SOCKS5 status */
+    switch (buffer[1])
+    {
+    case 0x00:
+        return NET_EOK;
+    case 0x01:
+        *errstr = xasprintf(_("proxy failure: %s"), _("general server failure"));
+        return NET_EPROXY;
+    case 0x02:
+        *errstr = xasprintf(_("proxy failure: %s"), _("connection not allowed"));
+        return NET_EPROXY;
+    case 0x03:
+        *errstr = xasprintf(_("proxy failure: %s"), _("network unreachable"));
+        return NET_EPROXY;
+    case 0x04:
+        *errstr = xasprintf(_("proxy failure: %s"), _("host unreachable"));
+        return NET_EPROXY;
+    case 0x05:
+        *errstr = xasprintf(_("proxy failure: %s"), _("connection refused"));
+        return NET_EPROXY;
+    case 0x06:
+        *errstr = xasprintf(_("proxy failure: %s"), _("time-to-live expired"));
+        return NET_EPROXY;
+    case 0x07:
+        *errstr = xasprintf(_("proxy failure: %s"), _("command not supported"));
+        return NET_EPROXY;
+    case 0x08:
+        *errstr = xasprintf(_("proxy failure: %s"), _("address type not supported"));
+        return NET_EPROXY;
+    default:
+        *errstr = xasprintf(_("proxy failure: %s"), _("unknown error"));
+        return NET_EPROXY;
+    }
+}
+
+int net_open_socket(
+        const char *proxy_hostname, int proxy_port,
+        const char *hostname, int port,
+        int timeout,
+        int *ret_fd, char **canonical_name, char **address,
+        char **errstr)
 {
     int fd;
     char *port_string;
@@ -357,6 +588,31 @@ int net_open_socket(const char *hostname, int port, int timeout, int *ret_fd,
     char *hostname_ascii;
 #endif
 
+    if (proxy_hostname)
+    {
+        error_code = net_open_socket(NULL, -1, proxy_hostname, proxy_port,
+                timeout, &fd, NULL, NULL, errstr);
+        if (error_code != NET_EOK)
+        {
+            return error_code;
+        }
+        error_code = net_socks5_connect(fd, hostname, port, errstr);
+        if (error_code != NET_EOK)
+        {
+            return error_code;
+        }
+        *ret_fd = fd;
+        if (canonical_name)
+        {
+            *canonical_name = NULL;
+        }
+        if (address)
+        {
+            *address = NULL;
+        }
+        return NET_EOK;
+    }
+
     hints.ai_family = PF_UNSPEC;
     hints.ai_socktype = SOCK_STREAM;
     hints.ai_flags = 0;
@@ -520,26 +776,11 @@ int net_open_socket(const char *hostname, int port, int timeout, int *ret_fd,
 int net_readbuf_read(int fd, readbuf_t *readbuf, char *ptr,
         char **errstr)
 {
-#ifdef W32_NATIVE
-
-    int e;
-
     if (readbuf->count <= 0)
     {
-        readbuf->count = recv(fd, readbuf->buf, sizeof(readbuf->buf), 0);
+        readbuf->count = net_recv(fd, readbuf->buf, sizeof(readbuf->buf), errstr);
         if (readbuf->count < 0)
         {
-            e = WSAGetLastError();
-            if (e == WSAEWOULDBLOCK)
-            {
-                *errstr = xasprintf(_("network read error: %s"),
-                        _("the operation timed out"));
-            }
-            else
-            {
-                *errstr = xasprintf(_("network read error: %s"),
-                        wsa_strerror(e));
-            }
             return -1;
         }
         else if (readbuf->count == 0)
@@ -551,41 +792,6 @@ int net_readbuf_read(int fd, readbuf_t *readbuf, char *ptr,
     readbuf->count--;
     *ptr = *((readbuf->ptr)++);
     return 1;
-
-#else /* !W32_NATIVE */
-
-    if (readbuf->count <= 0)
-    {
-        readbuf->count = (int)recv(fd, readbuf->buf, sizeof(readbuf->buf), 0);
-        if (readbuf->count < 0)
-        {
-            if (errno == EINTR)
-            {
-                *errstr = xasprintf(_("operation aborted"));
-            }
-            else if (errno == EAGAIN)
-            {
-                *errstr = xasprintf(_("network read error: %s"),
-                        _("the operation timed out"));
-            }
-            else
-            {
-                *errstr = xasprintf(_("network read error: %s"),
-                        strerror(errno));
-            }
-            return -1;
-        }
-        else if (readbuf->count == 0)
-        {
-            return 0;
-        }
-        readbuf->ptr = readbuf->buf;
-    }
-    readbuf->count--;
-    *ptr = *((readbuf->ptr)++);
-    return 1;
-
-#endif
 }
 
 
@@ -636,76 +842,25 @@ int net_gets(int fd, readbuf_t *readbuf,
 
 int net_puts(int fd, const char *s, size_t len, char **errstr)
 {
-#ifdef W32_NATIVE
-
-    int e, ret;
-
-    if (len < 1)
-    {
-        return NET_EOK;
-    }
-    if ((ret = send(fd, s, len, 0)) < 0)
-    {
-        e = WSAGetLastError();
-        if (e == WSAEWOULDBLOCK)
-        {
-            *errstr = xasprintf(_("network write error: %s"),
-                    _("the operation timed out"));
-        }
-        else
-        {
-            *errstr = xasprintf(_("network write error: %s"),
-                    wsa_strerror(e));
-        }
-        return NET_EIO;
-    }
-    else if ((size_t)ret == len)
-    {
-        return NET_EOK;
-    }
-    else /* 0 <= error_code < len */
-    {
-        *errstr = xasprintf(_("network write error"));
-        return NET_EIO;
-    }
-
-#else /* !W32_NATIVE */
-
-    ssize_t ret;
+    int ret;
 
     if (len < 1)
     {
         return NET_EOK;
     }
-    if ((ret = send(fd, s, len, 0)) < 0)
+    if ((ret = net_send(fd, s, len, errstr)) < 0)
     {
-        if (errno == EINTR)
-        {
-            *errstr = xasprintf(_("operation aborted"));
-        }
-        else if (errno == EAGAIN)
-        {
-            *errstr = xasprintf(_("network write error: %s"),
-                    _("the operation timed out"));
-        }
-        else
-        {
-            *errstr = xasprintf(_("network write error: %s"),
-                    strerror(errno));
-        }
         return NET_EIO;
     }
     else if ((size_t)ret == len)
     {
         return NET_EOK;
     }
-    else /* 0 <= error_code < len */
+    else /* 0 <= ret < len */
     {
         *errstr = xasprintf(_("network write error"));
         return NET_EIO;
     }
-
-#endif
 }
 
 
diff --git a/src/net.h b/src/net.h
index fde43d9..77c880b 100644
--- a/src/net.h
+++ b/src/net.h
@@ -3,7 +3,7 @@
  *
  * This file is part of msmtp, an SMTP client.
  *
- * Copyright (C) 2000, 2003, 2004, 2005, 2006, 2007, 2008
+ * Copyright (C) 2000, 2003, 2004, 2005, 2006, 2007, 2008, 2014
  * Martin Lambers <marlam@...23...>
  *
  *   This program is free software; you can redistribute it and/or modify
@@ -38,6 +38,7 @@
 #define NET_ESOCKET             3       /* Cannot create socket */
 #define NET_ECONNECT            4       /* Cannot connect */
 #define NET_EIO                 5       /* Input/output error */
+#define NET_EPROXY              6       /* Proxy failure */
 
 /*
  * net_lib_init()
@@ -52,6 +53,8 @@ int net_lib_init(char **errstr);
  * net_open_socket()
  *
  * Opens a TCP socket to 'hostname':'port'.
+ * 'proxy_hostname' and 'proxy_port' define a SOCKS5 proxy to use, unless they
+ * are NULL/-1, in which case no proxy will be used.
  * 'hostname' may be a host name or a network address.
  * 'timeout' is measured in secondes. If it is <= 0, no timeout will be set,
  * which means that the OS dependent default timeout value will be used.
@@ -66,10 +69,14 @@ int net_lib_init(char **errstr);
  * The strings must be deallocated when not used anymore.
  * The file descriptor is returned in 'fd'. It can be closed with close().
  *
- * Used error codes: NET_EHOSTNOTFOUND, NET_ESOCKET, NET_ECONNECT
+ * Used error codes: NET_EHOSTNOTFOUND, NET_ESOCKET, NET_ECONNECT, NET_EPROXY
  */
-int net_open_socket(const char *hostname, int port, int timeout, int *fd,
-        char **canonical_name, char **address, char **errstr);
+int net_open_socket(
+        const char *proxy_hostname, int proxy_port,
+        const char *hostname, int port,
+        int timeout,
+        int *fd, char **canonical_name, char **address,
+        char **errstr);
 
 /*
  * net_gets()
diff --git a/src/smtp.c b/src/smtp.c
index ee8550e..cd77371 100644
--- a/src/smtp.c
+++ b/src/smtp.c
@@ -130,7 +130,7 @@ int smtp_connect(smtp_server_t *srv, const char *host, int port, int timeout,
         char **server_canonical_name, char **server_address,
         char **errstr)
 {
-    return net_open_socket(host, port, timeout, &srv->fd,
+    return net_open_socket("localhost", 1080, host, port, timeout, &srv->fd,
             server_canonical_name, server_address, errstr);
 }