[Pkg-gnupg-commit] [libassuan] 02/11: Support SOCKS5 for assuan_sock_connect.
Eric Dorland
eric at moszumanska.debian.org
Thu Nov 12 16:14:46 UTC 2015
This is an automated email from the git hooks/post-receive script.
eric pushed a commit to branch master
in repository libassuan.
commit 85ece74a11718338dcd76d6e43ea8100183df02f
Author: Werner Koch <wk at gnupg.org>
Date: Sun Oct 18 16:24:34 2015 +0200
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 at gnupg.org>
---
configure.ac | 6 +-
doc/assuan.texi | 14 +++
src/assuan-socket.c | 240 ++++++++++++++++++++++++++++++++++++++++++++++++++--
tests/Makefile.am | 8 +-
tests/socks5.c | 240 ++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 496 insertions(+), 12 deletions(-)
diff --git a/configure.ac b/configure.ac
index 040ce7f..8e8768f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -126,6 +126,10 @@ AH_TOP([
/* Enable gpg-error's strerror macro under W32CE. */
#define GPG_ERR_ENABLE_ERRNO_MACROS 1
+
+/* Provide the es_ macro for estream. */
+#define GPGRT_ENABLE_ES_MACROS 1
+
])
AH_BOTTOM([
@@ -356,7 +360,7 @@ AM_PATH_GPG_ERROR(1.8,, AC_MSG_ERROR([libgpg-error was not found]))
#
# Checks for library functions.
#
-AC_CHECK_FUNCS([flockfile funlockfile inet_pton stat])
+AC_CHECK_FUNCS([flockfile funlockfile inet_pton stat getaddrinfo])
# On some systems (e.g. Solaris) nanosleep requires linking to librl.
# Given that we use nanosleep only as an optimization over a select
diff --git a/doc/assuan.texi b/doc/assuan.texi
index c822190..9161f0b 100644
--- a/doc/assuan.texi
+++ b/doc/assuan.texi
@@ -2082,6 +2082,20 @@ this flag for connecting to a Cygwin style socket because no state is
required at the client. On non-Windows platforms setting this flag is
ignored, reading the flag always returns a value of 0.
+ at item tor-mode
+ at itemx socks
+If @var{value} is 1 globally enable SOCKS5 mode for new connections
+using IPv6 or IPv4. @var{fd} must be set to @code{ASSUAN_INVALID_FD} A
+future extension may allow to disable SOCKS5 mode for a specified
+socket but globally disabling SOCKS5 mode is not possible. Using the
+flag ``tor-mode'' expects the SOCKS5 proxy to listen on port 9050, the
+flag ``socks'' expects the proxy to listen on port 1080.
+
+Connections to the loopback address are not routed though the SOCKS
+proxy. UDP requests are not supported at all. The proxy will be
+connected at address 127.0.0.1; an IPv6 connection to the proxy is not
+yet supported.
+
@end table
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
}
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 024ffe2..0ccb981 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -20,11 +20,13 @@
TESTS_ENVIRONMENT =
-EXTRA_DIST = motd ce-createpipe.c
+EXTRA_DIST = motd ce-createpipe.c $(testtools)
BUILT_SOURCES =
CLEANFILES =
+testtools = socks5
+
TESTS = version pipeconnect
if HAVE_W32CE_SYSTEM
@@ -35,10 +37,10 @@ if USE_DESCRIPTOR_PASSING
TESTS += fdpassing
endif
-
AM_CFLAGS = $(GPG_ERROR_CFLAGS)
+AM_LDFLAGS = -no-install
noinst_HEADERS = common.h
-noinst_PROGRAMS = $(TESTS) $(w32cetools)
+noinst_PROGRAMS = $(TESTS) $(w32cetools) $(testtools)
LDADD = ../src/libassuan.la $(NETLIBS) $(GPG_ERROR_LIBS)
diff --git a/tests/socks5.c b/tests/socks5.c
new file mode 100644
index 0000000..c179108
--- /dev/null
+++ b/tests/socks5.c
@@ -0,0 +1,240 @@
+/* socks5.c - Check the SOCKS5 client feature
+ * Copyright (C) 2015 g10 Code GmbH
+ *
+ * This file is part of Assuan.
+ *
+ * Assuan is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * Assuan is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#ifdef HAVE_W32_SYSTEM
+# ifdef HAVE_WINSOCK2_H
+# include <winsock2.h>
+# endif
+# include <windows.h>
+#else /*!HAVE_W32_SYSTEM*/
+# include <sys/types.h>
+# include <sys/socket.h>
+# include <netdb.h>
+#endif /*!HAVE_W32_SYSTEM*/
+
+#include "../src/assuan.h"
+#include "common.h"
+
+#ifndef HAVE_GETADDRINFO
+int
+main (void)
+{
+ fputs ("socks5: getaddrinfo not supported\n", stderr);
+ return 77; /* Skip test. */
+}
+#else /* HAVE_GETADDRINFO */
+
+
+/*
+
+ M A I N
+
+*/
+int
+main (int argc, char **argv)
+{
+ int last_argc = -1;
+ gpg_error_t err;
+ int only_v6 = 0;
+ int only_v4 = 0;
+ int use_tor = 0;
+ int disable_socks = 0;
+ assuan_fd_t sock = ASSUAN_INVALID_FD;
+ estream_t infp, outfp;
+ int c;
+
+ if (argc)
+ {
+ log_set_prefix (*argv);
+ argc--; argv++;
+ }
+ while (argc && last_argc != argc )
+ {
+ last_argc = argc;
+ if (!strcmp (*argv, "--"))
+ {
+ argc--; argv++;
+ break;
+ }
+ else if (!strcmp (*argv, "--help"))
+ {
+ puts (
+"usage: ./socks5 [options] HOST PORT\n"
+"\n"
+"Options:\n"
+" --verbose Show what is going on\n"
+" --use-tor Use port 9050 instead of 1080\n"
+" --inet6-only Use only IPv6\n"
+" --inet4-only Use only IPv4\n"
+" --disable-socks Connect w/o SOCKS\n"
+);
+ exit (0);
+ }
+ if (!strcmp (*argv, "--verbose"))
+ {
+ verbose = 1;
+ argc--; argv++;
+ }
+ else if (!strcmp (*argv, "--debug"))
+ {
+ verbose = debug = 1;
+ argc--; argv++;
+ }
+ else if (!strcmp (*argv, "-6") || !strcmp (*argv, "--inet6-only"))
+ {
+ only_v6 = 1;
+ argc--; argv++;
+ }
+ else if (!strcmp (*argv, "-4") || !strcmp (*argv, "--inet4-only"))
+ {
+ only_v4 = 1;
+ argc--; argv++;
+ }
+ else if (!strcmp (*argv, "--use-tor"))
+ {
+ use_tor = 1;
+ argc--; argv++;
+ }
+ else if (!strcmp (*argv, "--disable-socks"))
+ {
+ disable_socks = 1;
+ argc--; argv++;
+ }
+ else if (!strncmp (*argv, "--", 2))
+ {
+ log_error ("unknown option '%s'\n", *argv);
+ exit (1);
+ }
+ }
+
+ if (argc != 2)
+ {
+ fputs ("usage: socks5 HOST PORT\n", stderr);
+ exit (1);
+ }
+
+ assuan_set_assuan_log_prefix (log_prefix);
+
+ if (!assuan_check_version (ASSUAN_VERSION))
+ log_error ("assuan_check_version returned an error\n");
+
+ assuan_sock_init ();
+
+ if (!disable_socks
+ && assuan_sock_set_flag (ASSUAN_INVALID_FD,
+ use_tor? "tor-mode":"socks", 1))
+ {
+ err = gpg_error_from_syserror ();
+ log_fatal ("setting %s mode failed: %s\n",
+ use_tor? "TOR": "SOCKS", gpg_strerror (err));
+ }
+
+ {
+ struct addrinfo hints, *res, *ai;
+ int ret;
+ int anyok = 0;
+
+ memset (&hints, 0, sizeof (hints));
+ hints.ai_socktype = SOCK_STREAM;
+ ret = getaddrinfo (argv[0], argv[1], &hints, &res);
+ if (ret)
+ {
+ log_error ("error resolving '%s': %s\n", argv[0], gai_strerror (ret));
+ exit (1);
+ }
+
+ for (ai = res; ai; ai = ai->ai_next)
+ {
+ if (ai->ai_family == AF_INET && only_v6)
+ continue;
+ if (ai->ai_family == AF_INET6 && only_v4)
+ continue;
+
+ if (sock != ASSUAN_INVALID_FD)
+ assuan_sock_close (sock);
+ sock = assuan_sock_new (ai->ai_family, ai->ai_socktype,
+ ai->ai_protocol);
+ if (sock == ASSUAN_INVALID_FD)
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("error creating socket: %s\n", gpg_strerror (err));
+ freeaddrinfo (res);
+ exit (1);
+ }
+
+ if (assuan_sock_connect (sock, ai->ai_addr, ai->ai_addrlen))
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("assuan_sock_connect (%s) failed: %s\n",
+ ai->ai_family == AF_INET6? "v6" :
+ ai->ai_family == AF_INET ? "v4" : "?",
+ gpg_strerror (err));
+ }
+ else
+ {
+ log_info ("assuan_sock_connect succeeded (%d)\n",
+ ai->ai_family == AF_INET6? "v6" :
+ ai->ai_family == AF_INET ? "v4" : "?");
+ anyok = 1;
+ break;
+ }
+ }
+ freeaddrinfo (res);
+ if (!anyok)
+ exit (1);
+ }
+
+ infp = es_fdopen_nc (sock, "rb");
+ if (!infp)
+ {
+ err = gpg_error_from_syserror ();
+ assuan_sock_close (sock);
+ log_fatal ("opening inbound stream failed: %s\n", gpg_strerror (err));
+ }
+ outfp = es_fdopen (sock, "wb");
+ if (!outfp)
+ {
+ err = gpg_error_from_syserror ();
+ es_fclose (infp);
+ assuan_sock_close (sock);
+ log_fatal ("opening outbound stream failed: %s\n", gpg_strerror (err));
+ }
+
+ es_fputs ("HEAD / HTTP/1.0\r\n\r\n", outfp);
+ es_fflush (outfp);
+ while ((c = es_fgetc (infp)) != EOF)
+ {
+ putchar (c);
+ if (c == '\n')
+ break;
+ }
+ es_fclose (infp);
+ es_fclose (outfp);
+
+ return errorcount ? 1 : 0;
+}
+#endif /*HAVE_GETADDRINFO*/
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-gnupg/libassuan.git
More information about the Pkg-gnupg-commit
mailing list