diff options
author | Werner Koch <[email protected]> | 2015-10-18 14:24:34 +0000 |
---|---|---|
committer | Werner Koch <[email protected]> | 2015-10-18 14:37:52 +0000 |
commit | 85ece74a11718338dcd76d6e43ea8100183df02f (patch) | |
tree | d4e880ffea00bc432aff88b9331d387957340e5e | |
parent | Post release updates (diff) | |
download | libassuan-85ece74a11718338dcd76d6e43ea8100183df02f.tar.gz libassuan-85ece74a11718338dcd76d6e43ea8100183df02f.zip |
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 <[email protected]>
-rw-r--r-- | configure.ac | 6 | ||||
-rw-r--r-- | doc/assuan.texi | 14 | ||||
-rw-r--r-- | src/assuan-socket.c | 240 | ||||
-rw-r--r-- | tests/Makefile.am | 8 | ||||
-rw-r--r-- | 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. +@item tor-mode +@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*/ |