summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS14
-rw-r--r--doc/assuan.texi16
-rw-r--r--src/assuan-socket.c177
-rw-r--r--src/assuan.h.in12
-rw-r--r--src/libassuan.def1
-rw-r--r--src/libassuan.vers1
-rw-r--r--tests/socks5.c183
7 files changed, 324 insertions, 80 deletions
diff --git a/NEWS b/NEWS
index 934feda..01fa3b6 100644
--- a/NEWS
+++ b/NEWS
@@ -1,6 +1,18 @@
-Noteworthy changes in version 2.3.1 (unreleased) [C6/A6/R_]
+Noteworthy changes in version 2.4.0 (unreleased) [C6/A6/R_]
------------------------------------------------
+ * New flags "socks" and "tor-mode" for assuan_sock_{set,get}_flag.
+
+ * New function assuan_sock_connect_byname.
+
+ * Interface changes relative to the 2.3.0 release:
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ assuan_sock_connect_byname NEW.
+ ASSUAN_SOCK_TOR NEW.
+ ASSUAN_SOCK_SOCKS NEW.
+ assuan_sock_set_flag EXTENDED.
+ assuan_sock_get_flag EXTENDED.
+
Noteworthy changes in version 2.3.0 (2015-08-28) [C6/A6/R0]
------------------------------------------------
diff --git a/doc/assuan.texi b/doc/assuan.texi
index 9161f0b..8445eb4 100644
--- a/doc/assuan.texi
+++ b/doc/assuan.texi
@@ -1979,6 +1979,22 @@ details on the redirection file format.
@end deftypefun
+@deftypefun int assuan_sock_connect_byname (@w{const char * @var{host}}, @
+ @w{unsigned short @var{port}}, @
+ @w{int @var{reserved}}, @
+ @w{const char *@var{credentials}}, @
+ @w{unsigned int @var{flags}})
+
+Directly connect to @var{port} on @var{host} given as a name. The
+current implementation requires that @var{flags} has either
+@code{ASSUAN_SOCK_SOCKS} or @code{ASSUAN_SOCK_TOR} set. On success a
+new TCP STREAM socket is returned; on error @code{ASSUAN_INVALID_FD}
+and ERRNO set. If @var{credentials} is not @code{NULL}, it is a
+string used for password based SOCKS authentication. Username and
+password are separated by a colon. @var{reserved} should be 0.
+@end deftypefun
+
+
@deftypefun int assuan_sock_bind ( @
@w{assuan_fd_t @var{sockfd}}, @
@w{struct sockaddr *@var{addr}}, @
diff --git a/src/assuan-socket.c b/src/assuan-socket.c
index 9a6ee66..12e9e38 100644
--- a/src/assuan-socket.c
+++ b/src/assuan-socket.c
@@ -657,9 +657,11 @@ do_writen (assuan_context_t ctx, assuan_fd_t sockfd,
}
-/* Connect using the SOCKS5 protocol. */
+/* Connect using the SOCKS5 protocol. */
static int
-socks5_connect (assuan_context_t ctx, int sock,
+socks5_connect (assuan_context_t ctx, int sock, unsigned short socksport,
+ const char *credentials,
+ const char *hostname, unsigned short hostport,
struct sockaddr *addr, socklen_t length)
{
int ret;
@@ -669,16 +671,36 @@ socks5_connect (assuan_context_t ctx, int sock,
size_t proxyaddrlen;
struct sockaddr_in6 *addr_in6;
struct sockaddr_in *addr_in;
- unsigned char buffer[22];
- size_t buflen;
+ unsigned char buffer[22+512]; /* The extra 512 gives enough space
+ for username/password or the
+ hostname. */
+ size_t buflen, hostnamelen;
+ int method;
/* memset (&proxyaddr_in6, 0, sizeof proxyaddr_in6); */
memset (&proxyaddr_in, 0, sizeof proxyaddr_in);
+ /* Either HOSTNAME or ADDR may be given. */
+ if (hostname && addr)
+ {
+ gpg_err_set_errno (EINVAL);
+ return -1;
+ }
+
+ /* If a hostname is given it must fit into our buffer and it must be
+ less than 256 so that its length can be encoded in one byte. */
+ hostnamelen = hostname? strlen (hostname) : 0;
+ if (hostnamelen > 255)
+ {
+ gpg_err_set_errno (ENAMETOOLONG);
+ return -1;
+ }
+
/* Connect to local host. */
- /* Fixme: First try to use IPv6. */
+ /* Fixme: First try to use IPv6 but note that
+ _assuan_sock_connect_byname created the socket with AF_INET. */
proxyaddr_in.sin_family = AF_INET;
- proxyaddr_in.sin_port = htons (tor_mode);
+ proxyaddr_in.sin_port = htons (socksport);
proxyaddr_in.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
proxyaddr = (struct sockaddr *)&proxyaddr_in;
proxyaddrlen = sizeof proxyaddr_in;
@@ -687,7 +709,11 @@ socks5_connect (assuan_context_t ctx, int sock,
return ret;
buffer[0] = 5; /* RFC-1928 VER field. */
buffer[1] = 1; /* NMETHODS */
- buffer[2] = 0; /* Method: No authentication required. */
+ if (credentials)
+ method = 2; /* Method: username/password authentication. */
+ else
+ method = 0; /* Method: No authentication required. */
+ buffer[2] = method;
/* Negotiate method. */
ret = do_writen (ctx, sock, buffer, 3);
@@ -696,7 +722,7 @@ socks5_connect (assuan_context_t ctx, int sock,
ret = do_readn (ctx, sock, buffer, 2);
if (ret)
return ret;
- if (buffer[0] != 5 || buffer[1] != 0 )
+ if (buffer[0] != 5 || buffer[1] != method )
{
/* Socks server returned wrong version or does not support our
requested method. */
@@ -704,11 +730,70 @@ socks5_connect (assuan_context_t ctx, int sock,
return -1;
}
+ if (credentials)
+ {
+ const char *password;
+ int ulen, plen;
+
+ password = strchr (credentials, ':');
+ if (!password)
+ {
+ gpg_err_set_errno (EINVAL); /* No password given. */
+ return -1;
+ }
+ ulen = password - credentials;
+ password++;
+ plen = strlen (password);
+ if (!ulen || ulen > 255 || !plen || plen > 255)
+ {
+ gpg_err_set_errno (EINVAL);
+ return -1;
+ }
+
+ buffer[0] = 1; /* VER of the sub-negotiation. */
+ buffer[1] = ulen;
+ buflen = 2;
+ memcpy (buffer+buflen, credentials, ulen);
+ buflen += ulen;
+ buffer[buflen++] = plen;
+ memcpy (buffer+buflen, password, plen);
+ buflen += plen;
+ ret = do_writen (ctx, sock, buffer, buflen);
+ wipememory (buffer, buflen);
+ if (ret)
+ return ret;
+ ret = do_readn (ctx, sock, buffer, 2);
+ if (ret)
+ return ret;
+ if (buffer[0] != 1)
+ {
+ /* SOCKS server returned wrong version. */
+ gpg_err_set_errno (EPROTO);
+ return -1;
+ }
+ if (buffer[1])
+ {
+ /* SOCKS server denied access. */
+ gpg_err_set_errno (EACCES);
+ 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)
+ if (hostname)
+ {
+ buffer[3] = 3; /* ATYP = DOMAINNAME */
+ buflen = 4;
+ buffer[buflen++] = hostnamelen;
+ memcpy (buffer+buflen, hostname, hostnamelen);
+ buflen += hostnamelen;
+ buffer[buflen++] = (hostport >> 8); /* DST.PORT */
+ buffer[buflen++] = hostport;
+ }
+ else if (addr->sa_family == AF_INET6)
{
addr_in6 = (struct sockaddr_in6 *)addr;
@@ -729,7 +814,7 @@ socks5_connect (assuan_context_t ctx, int sock,
ret = do_writen (ctx, sock, buffer, buflen);
if (ret)
return ret;
- ret = do_readn (ctx, sock, buffer, buflen);
+ ret = do_readn (ctx, sock, buffer, 10 /* Length for IPv4 */);
if (ret)
return ret;
if (buffer[0] != 5 || buffer[2] != 0 )
@@ -743,10 +828,10 @@ socks5_connect (assuan_context_t ctx, int sock,
{
switch (buffer[1])
{
- case 0x01: /* general SOCKS server failure. */
+ case 0x01: /* General SOCKS server failure. */
gpg_err_set_errno (ENETDOWN);
break;
- case 0x02: /* connection not allowed by ruleset. */
+ case 0x02: /* Connection not allowed by ruleset. */
gpg_err_set_errno (EACCES);
break;
case 0x03: /* Network unreachable */
@@ -770,6 +855,15 @@ socks5_connect (assuan_context_t ctx, int sock,
}
return -1;
}
+ if (buffer[3] == 4)
+ {
+ /* ATYP indicates a v6 address. We need to read the remaining
+ 12 bytes. */
+ ret = do_readn (ctx, sock, buffer+10, 12);
+ if (ret)
+ return ret;
+ }
+
/* FIXME: We have not way to store the actual address used by the
server. */
@@ -779,7 +873,7 @@ socks5_connect (assuan_context_t ctx, int sock,
/* 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
+ is enabled and the desired address is not the loopback
address. */
static int
use_socks (struct sockaddr *addr)
@@ -874,7 +968,8 @@ _assuan_sock_connect (assuan_context_t ctx, assuan_fd_t sockfd,
}
else if (use_socks (addr))
{
- return socks5_connect (ctx, HANDLE2SOCKET (sockfd), addr, addrlen);
+ return socks5_connect (ctx, HANDLE2SOCKET (sockfd), tor_mode,
+ NULL, NULL, 0, addr, addrlen);
}
else
{
@@ -916,7 +1011,8 @@ _assuan_sock_connect (assuan_context_t ctx, assuan_fd_t sockfd,
if (use_socks (addr))
{
- return socks5_connect (ctx, sockfd, addr, addrlen);
+ return socks5_connect (ctx, sockfd, tor_mode,
+ NULL, NULL, 0, addr, addrlen);
}
else
{
@@ -926,6 +1022,48 @@ _assuan_sock_connect (assuan_context_t ctx, assuan_fd_t sockfd,
}
+/* Connect to HOST specified as host name on PORT. The current
+ implementation requires that either the flags ASSUAN_SOCK_SOCKS or
+ ASSUAN_SOCK_TOR are give in FLAGS. On success a new socket is
+ returned; on error ASSUAN_INVALID_FD is returned and ERRNO set. If
+ CREDENTIALS is not NULL, it is a string used for password based
+ authentication. Username and password are separated by a
+ colon. RESERVED must be 0. */
+assuan_fd_t
+_assuan_sock_connect_byname (assuan_context_t ctx, const char *host,
+ unsigned short port, int reserved,
+ const char *credentials, unsigned int flags)
+{
+ int fd;
+ unsigned short socksport;
+
+ if ((flags & ASSUAN_SOCK_TOR))
+ socksport = TOR_PORT;
+ else if ((flags & ASSUAN_SOCK_SOCKS))
+ socksport = SOCKS_PORT;
+ else
+ {
+ gpg_err_set_errno (ENOTSUP);
+ return ASSUAN_INVALID_FD;
+ }
+
+ fd = _assuan_sock_new (ctx, AF_INET, SOCK_STREAM, 0);
+ if (fd == ASSUAN_INVALID_FD)
+ return fd;
+
+ if (socks5_connect (ctx, fd, socksport,
+ credentials, host, port, NULL, 0))
+ {
+ int save_errno = errno;
+ assuan_sock_close (fd);
+ gpg_err_set_errno (save_errno);
+ return -1;
+ }
+
+ return fd;
+}
+
+
int
_assuan_sock_bind (assuan_context_t ctx, assuan_fd_t sockfd,
struct sockaddr *addr, int addrlen)
@@ -1257,6 +1395,15 @@ assuan_sock_connect (assuan_fd_t sockfd, struct sockaddr *addr, int addrlen)
return _assuan_sock_connect (sock_ctx, sockfd, addr, addrlen);
}
+assuan_fd_t
+assuan_sock_connect_byname (const char *host, unsigned short port,
+ int reserved, const char *credentials,
+ unsigned int flags)
+{
+ return _assuan_sock_connect_byname (sock_ctx,
+ host, port, reserved, credentials, flags);
+}
+
int
assuan_sock_bind (assuan_fd_t sockfd, struct sockaddr *addr, int addrlen)
{
diff --git a/src/assuan.h.in b/src/assuan.h.in
index b26fa3b..67a1c20 100644
--- a/src/assuan.h.in
+++ b/src/assuan.h.in
@@ -461,6 +461,14 @@ gpg_error_t assuan_set_error (assuan_context_t ctx, gpg_error_t err,
/*-- assuan-socket.c --*/
+/* This flag is used with assuan_sock_connect_byname to
+ connect via SOCKS. */
+#define ASSUAN_SOCK_SOCKS 1
+/* This flag is used with assuan_sock_connect_byname to force a
+ connection via Tor even if the socket subsystem has not been
+ swicthed into Tor mode. This flags overrides ASSUAN_SOCK_SOCKS. */
+#define ASSUAN_SOCK_TOR 2
+
/* These are socket wrapper functions to support an emulation of Unix
domain sockets on Windows W32. */
gpg_error_t assuan_sock_init (void);
@@ -471,6 +479,10 @@ int assuan_sock_set_flag (assuan_fd_t sockfd, const char *name, int value);
int assuan_sock_get_flag (assuan_fd_t sockfd, const char *name, int *r_value);
int assuan_sock_connect (assuan_fd_t sockfd,
struct sockaddr *addr, int addrlen);
+assuan_fd_t assuan_sock_connect_byname (const char *host, unsigned short port,
+ int reserved,
+ const char *credentials,
+ unsigned int flags);
int assuan_sock_bind (assuan_fd_t sockfd, struct sockaddr *addr, int addrlen);
int assuan_sock_set_sockaddr_un (const char *fname, struct sockaddr *addr,
int *r_redirected);
diff --git a/src/libassuan.def b/src/libassuan.def
index 9f31c31..c320151 100644
--- a/src/libassuan.def
+++ b/src/libassuan.def
@@ -114,6 +114,7 @@ EXPORTS
assuan_sock_set_sockaddr_un @93
assuan_sock_set_flag @94
assuan_sock_get_flag @95
+ assuan_sock_connect_byname @96
; END
diff --git a/src/libassuan.vers b/src/libassuan.vers
index 2b2389d..37c0131 100644
--- a/src/libassuan.vers
+++ b/src/libassuan.vers
@@ -104,6 +104,7 @@ LIBASSUAN_1.0 {
assuan_sock_set_sockaddr_un;
assuan_sock_set_flag;
assuan_sock_get_flag;
+ assuan_sock_connect_byname;
__assuan_close;
__assuan_pipe;
diff --git a/tests/socks5.c b/tests/socks5.c
index c179108..7aa2b71 100644
--- a/tests/socks5.c
+++ b/tests/socks5.c
@@ -63,6 +63,9 @@ main (int argc, char **argv)
int only_v4 = 0;
int use_tor = 0;
int disable_socks = 0;
+ int opt_byname = 0;
+ const char *user = NULL;
+ const char *pass = NULL;
assuan_fd_t sock = ASSUAN_INVALID_FD;
estream_t infp, outfp;
int c;
@@ -83,15 +86,18 @@ main (int argc, char **argv)
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"
-);
+ "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"
+ " --byname Use assuan_sock_connect_byname\n"
+ " --user STRING Use STRING as user for authentication\n"
+ " --pass STRING Use STRING as password for authentication\n"
+ );
exit (0);
}
if (!strcmp (*argv, "--verbose"))
@@ -124,6 +130,29 @@ main (int argc, char **argv)
disable_socks = 1;
argc--; argv++;
}
+ else if (!strcmp (*argv, "--byname"))
+ {
+ opt_byname = 1;
+ argc--; argv++;
+ }
+ else if (!strcmp (*argv, "--user"))
+ {
+ argc--; argv++;
+ if (argc)
+ {
+ user = *argv;
+ argc--; argv++;
+ }
+ }
+ else if (!strcmp (*argv, "--pass"))
+ {
+ argc--; argv++;
+ if (argc)
+ {
+ pass = *argv;
+ argc--; argv++;
+ }
+ }
else if (!strncmp (*argv, "--", 2))
{
log_error ("unknown option '%s'\n", *argv);
@@ -153,60 +182,86 @@ main (int argc, char **argv)
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));
+ if (opt_byname)
+ {
+ unsigned short port;
+ char *cred;
+
+ if (user || pass)
+ cred = xstrconcat (user?user:"", ":", pass, NULL);
+ else
+ cred = NULL;
+
+ port = strtoul (argv[1], NULL, 10);
+ if (port < 0 || port > 65535)
+ log_fatal ("port number out of range\n");
+
+ sock = assuan_sock_connect_byname (argv[0], port, 0, cred,
+ ASSUAN_SOCK_TOR);
+ if (sock == ASSUAN_INVALID_FD)
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("assuan_sock_connect_byname (%s) failed: %s\n",
+ argv[0], gpg_strerror (err));
+ exit (1);
+ }
+ xfree (cred);
+ }
+ else
+ {
+ 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 (%s)\n",
+ ai->ai_family == AF_INET6? "v6" :
+ ai->ai_family == AF_INET ? "v4" : "?");
+ anyok = 1;
+ break;
+ }
+ }
+ freeaddrinfo (res);
+ if (!anyok)
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)
@@ -220,8 +275,8 @@ main (int argc, char **argv)
{
err = gpg_error_from_syserror ();
es_fclose (infp);
- assuan_sock_close (sock);
- log_fatal ("opening outbound stream failed: %s\n", gpg_strerror (err));
+ 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);