[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);
}