summaryrefslogtreecommitdiff
path: root/src/assuan-socket.c
diff options
context:
space:
mode:
authorWerner Koch <wk@gnupg.org>2015-10-18 16:24:34 +0200
committerWerner Koch <wk@gnupg.org>2015-10-18 16:37:52 +0200
commit85ece74a11718338dcd76d6e43ea8100183df02f (patch)
treed4e880ffea00bc432aff88b9331d387957340e5e /src/assuan-socket.c
parent3aec1981cfd0a7b29750965c065a45ad928e66dc (diff)
downloadlibassuan-85ece74a11718338dcd76d6e43ea8100183df02f.tar.gz
Support SOCKS5 for assuan_sock_connect.
* src/assuan-socket.c: Include netinet/in.h and arpa/inet.h. (SOCKS_PORT, TOR_PORT): New constants. (tor_mode): New variable. (_assuan_sock_set_flag): Add flags "tor-mode" and "socks". (_assuan_sock_get_flag): Ditto. (do_readn, do_writen): Always build. (socks5_connect): New. (use_socks): New. (_assuan_sock_connect): Divert to socks5_connect if requested. * tests/socks5.c: New. * configure.ac (AH_TOP): Define GPGRT_ENABLE_ES_MACROS. (AC_CHECK_FUNC): Check for getaddrinfo. * tests/Makefile.am (testtools): New. Add socks5. (AM_LDFLAGS): Add -no-install for easier debugging. -- A future extension might be a new assuan_sock_direct_connect call takes the hostname as a string and returns a new socket. This allows the proxy to do the resolving. However, in the long term these socket wrapper should be moved to libgpgrt (aka libgpg-error). Signed-off-by: Werner Koch <wk@gnupg.org>
Diffstat (limited to 'src/assuan-socket.c')
-rw-r--r--src/assuan-socket.c240
1 files changed, 232 insertions, 8 deletions
diff --git a/src/assuan-socket.c b/src/assuan-socket.c
index ae90802..9a6ee66 100644
--- a/src/assuan-socket.c
+++ b/src/assuan-socket.c
@@ -34,6 +34,8 @@
#else
# include <sys/types.h>
# include <sys/socket.h>
+# include <netinet/in.h>
+# include <arpa/inet.h>
#endif
#include <errno.h>
#ifdef HAVE_SYS_STAT_H
@@ -74,11 +76,17 @@
# define ENAMETOOLONG EINVAL
#endif
+
#ifndef SUN_LEN
# define SUN_LEN(ptr) ((size_t) (((struct sockaddr_un *) 0)->sun_path) \
+ strlen ((ptr)->sun_path))
#endif
+
+/* The standard SOCKS and TOR port. */
+#define SOCKS_PORT 1080
+#define TOR_PORT 9050
+
/* In the future, we can allow access to sock_ctx, if that context's
hook functions need to be overridden. There can only be one global
assuan_sock_* user (one library or one application) with this
@@ -86,6 +94,12 @@
needed. */
static assuan_context_t sock_ctx;
+/* This global flag can be set using assuan_sock_set_flag to enable
+ TOR or SOCKS mode for all sockets. It may not be reset. The value
+ is the port to be used. */
+static unsigned short tor_mode;
+
+
#ifdef HAVE_W32_SYSTEM
/* A table of active Cygwin connections. This is only used for
@@ -498,8 +512,10 @@ _assuan_sock_new (assuan_context_t ctx, int domain, int type, int proto)
int
_assuan_sock_set_flag (assuan_context_t ctx, assuan_fd_t sockfd,
- const char *name, int value)
+ const char *name, int value)
{
+ (void)ctx;
+
if (!strcmp (name, "cygwin"))
{
#ifdef HAVE_W32_SYSTEM
@@ -511,6 +527,39 @@ _assuan_sock_set_flag (assuan_context_t ctx, assuan_fd_t sockfd,
/* Setting the Cygwin flag on non-Windows is ignored. */
#endif
}
+ else if (!strcmp (name, "tor-mode") || !strcmp (name, "socks"))
+ {
+ /* If SOCKFD is ASSUAN_INVALID_FD this controls global flag to
+ switch AF_INET and AF_INET6 into TOR mode by using a SOCKS5
+ proxy on localhost:9050. It may only be switched on and this
+ needs to be done before any new threads are started. Once
+ TOR mode has been enabled, TOR mode can be disabled for a
+ specific socket by using SOCKFD with a VALUE of 0. */
+ if (sockfd == ASSUAN_INVALID_FD)
+ {
+ if (tor_mode && !value)
+ {
+ gpg_err_set_errno (EPERM);
+ return -1; /* Clearing the global flag is not allowed. */
+ }
+ else if (value)
+ {
+ if (*name == 's')
+ tor_mode = SOCKS_PORT;
+ else
+ tor_mode = TOR_PORT;
+ }
+ }
+ else if (tor_mode && sockfd != ASSUAN_INVALID_FD)
+ {
+ /* Fixme: Disable/enable tormode for the given context. */
+ }
+ else
+ {
+ gpg_err_set_errno (EINVAL);
+ return -1;
+ }
+ }
else
{
gpg_err_set_errno (EINVAL);
@@ -535,6 +584,15 @@ _assuan_sock_get_flag (assuan_context_t ctx, assuan_fd_t sockfd,
*r_value = 0;
#endif
}
+ else if (!strcmp (name, "tor-mode"))
+ {
+ /* FIXME: Find tor-mode for the given socket. */
+ *r_value = tor_mode == TOR_PORT;
+ }
+ else if (!strcmp (name, "socks"))
+ {
+ *r_value = tor_mode == SOCKS_PORT;
+ }
else
{
gpg_err_set_errno (EINVAL);
@@ -547,7 +605,6 @@ _assuan_sock_get_flag (assuan_context_t ctx, assuan_fd_t sockfd,
/* Read NBYTES from SOCKFD into BUFFER. Return 0 on success. Handle
EAGAIN and EINTR. */
-#ifdef HAVE_W32_SYSTEM
static int
do_readn (assuan_context_t ctx, assuan_fd_t sockfd,
void *buffer, size_t nbytes)
@@ -561,7 +618,7 @@ do_readn (assuan_context_t ctx, assuan_fd_t sockfd,
if (n < 0 && errno == EINTR)
;
else if (n < 0 && errno == EAGAIN)
- Sleep (100);
+ _assuan_usleep (ctx, 100000); /* 100ms */
else if (n < 0)
return -1;
else if (!n)
@@ -598,7 +655,164 @@ do_writen (assuan_context_t ctx, assuan_fd_t sockfd,
return ret;
}
-#endif /*HAVE_W32_SYSTEM*/
+
+
+/* Connect using the SOCKS5 protocol. */
+static int
+socks5_connect (assuan_context_t ctx, int sock,
+ struct sockaddr *addr, socklen_t length)
+{
+ int ret;
+ /* struct sockaddr_in6 proxyaddr_in6; */
+ struct sockaddr_in proxyaddr_in;
+ struct sockaddr *proxyaddr;
+ size_t proxyaddrlen;
+ struct sockaddr_in6 *addr_in6;
+ struct sockaddr_in *addr_in;
+ unsigned char buffer[22];
+ size_t buflen;
+
+ /* memset (&proxyaddr_in6, 0, sizeof proxyaddr_in6); */
+ memset (&proxyaddr_in, 0, sizeof proxyaddr_in);
+
+ /* Connect to local host. */
+ /* Fixme: First try to use IPv6. */
+ proxyaddr_in.sin_family = AF_INET;
+ proxyaddr_in.sin_port = htons (tor_mode);
+ proxyaddr_in.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
+ proxyaddr = (struct sockaddr *)&proxyaddr_in;
+ proxyaddrlen = sizeof proxyaddr_in;
+ ret = _assuan_connect (ctx, sock, proxyaddr, proxyaddrlen);
+ if (ret)
+ return ret;
+ buffer[0] = 5; /* RFC-1928 VER field. */
+ buffer[1] = 1; /* NMETHODS */
+ buffer[2] = 0; /* Method: No authentication required. */
+
+ /* Negotiate method. */
+ ret = do_writen (ctx, sock, buffer, 3);
+ if (ret)
+ return ret;
+ ret = do_readn (ctx, sock, buffer, 2);
+ if (ret)
+ return ret;
+ if (buffer[0] != 5 || buffer[1] != 0 )
+ {
+ /* Socks server returned wrong version or does not support our
+ requested method. */
+ gpg_err_set_errno (ENOTSUP); /* Fixme: Is there a better errno? */
+ return -1;
+ }
+
+ /* Send request details (rfc-1928, 4). */
+ buffer[0] = 5; /* VER */
+ buffer[1] = 1; /* CMD = CONNECT */
+ buffer[2] = 0; /* RSV */
+ if (addr->sa_family == AF_INET6)
+ {
+ addr_in6 = (struct sockaddr_in6 *)addr;
+
+ buffer[3] = 4; /* ATYP = IPv6 */
+ memcpy (buffer+ 4, &addr_in6->sin6_addr.s6_addr, 16); /* DST.ADDR */
+ memcpy (buffer+20, &addr_in6->sin6_port, 2); /* DST.PORT */
+ buflen = 22;
+ }
+ else
+ {
+ addr_in = (struct sockaddr_in *)addr;
+
+ buffer[3] = 1; /* ATYP = IPv4 */
+ memcpy (buffer+4, &addr_in->sin_addr.s_addr, 4); /* DST.ADDR */
+ memcpy (buffer+8, &addr_in->sin_port, 2); /* DST.PORT */
+ buflen = 10;
+ }
+ ret = do_writen (ctx, sock, buffer, buflen);
+ if (ret)
+ return ret;
+ ret = do_readn (ctx, sock, buffer, buflen);
+ if (ret)
+ return ret;
+ if (buffer[0] != 5 || buffer[2] != 0 )
+ {
+ /* Socks server returned wrong version or the reserved field is
+ not zero. */
+ gpg_err_set_errno (EPROTO);
+ return -1;
+ }
+ if (buffer[1])
+ {
+ switch (buffer[1])
+ {
+ case 0x01: /* general SOCKS server failure. */
+ gpg_err_set_errno (ENETDOWN);
+ break;
+ case 0x02: /* connection not allowed by ruleset. */
+ gpg_err_set_errno (EACCES);
+ break;
+ case 0x03: /* Network unreachable */
+ gpg_err_set_errno (ENETUNREACH);
+ break;
+ case 0x04: /* Host unreachable */
+ gpg_err_set_errno (EHOSTUNREACH);
+ break;
+ case 0x05: /* Connection refused */
+ gpg_err_set_errno (ECONNREFUSED);
+ break;
+ case 0x06: /* TTL expired */
+ gpg_err_set_errno (ETIMEDOUT);
+ break;
+ case 0x08: /* Address type not supported */
+ gpg_err_set_errno (EPROTONOSUPPORT);
+ break;
+ case 0x07: /* Command not supported */
+ default:
+ gpg_err_set_errno (ENOTSUP); /* Fixme: Is there a better errno? */
+ }
+ return -1;
+ }
+ /* FIXME: We have not way to store the actual address used by the
+ server. */
+
+
+ return 0;
+}
+
+
+/* Return true if SOCKS shall be used. This is the case if tor_mode
+ is enabled and and the desired address is not the loopback
+ address. */
+static int
+use_socks (struct sockaddr *addr)
+{
+ if (!tor_mode)
+ return 0;
+ else if (addr->sa_family == AF_INET6)
+ {
+ struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)addr;
+ const unsigned char *s;
+ int i;
+
+ s = (unsigned char *)&addr_in6->sin6_addr.s6_addr;
+ if (s[15] != 1)
+ return 1; /* Last octet is not 1 - not the loopback address. */
+ for (i=0; i < 15; i++, s++)
+ if (*s)
+ return 1; /* Non-zero octet found - not the loopback address. */
+
+ return 0; /* This is the loopback address. */
+ }
+ else if (addr->sa_family == AF_INET)
+ {
+ struct sockaddr_in *addr_in = (struct sockaddr_in *)addr;
+
+ if (*(unsigned char*)&addr_in->sin_addr.s_addr == 127)
+ return 0; /* Loopback (127.0.0.0/8) */
+
+ return 1;
+ }
+ else
+ return 0;
+}
int
@@ -658,11 +872,13 @@ _assuan_sock_connect (assuan_context_t ctx, assuan_fd_t sockfd,
}
return ret;
}
+ else if (use_socks (addr))
+ {
+ return socks5_connect (ctx, HANDLE2SOCKET (sockfd), addr, addrlen);
+ }
else
{
- int ret;
- ret = _assuan_connect (ctx, HANDLE2SOCKET (sockfd), addr, addrlen);
- return ret;
+ return _assuan_connect (ctx, HANDLE2SOCKET (sockfd), addr, addrlen);
}
#else
# if HAVE_STAT
@@ -697,7 +913,15 @@ _assuan_sock_connect (assuan_context_t ctx, assuan_fd_t sockfd,
}
# endif /*HAVE_STAT*/
- return _assuan_connect (ctx, sockfd, addr, addrlen);
+
+ if (use_socks (addr))
+ {
+ return socks5_connect (ctx, sockfd, addr, addrlen);
+ }
+ else
+ {
+ return _assuan_connect (ctx, sockfd, addr, addrlen);
+ }
#endif
}