diff options
-rw-r--r-- | NEWS | 14 | ||||
-rw-r--r-- | doc/assuan.texi | 16 | ||||
-rw-r--r-- | src/assuan-socket.c | 177 | ||||
-rw-r--r-- | src/assuan.h.in | 12 | ||||
-rw-r--r-- | src/libassuan.def | 1 | ||||
-rw-r--r-- | src/libassuan.vers | 1 | ||||
-rw-r--r-- | tests/socks5.c | 183 |
7 files changed, 324 insertions, 80 deletions
@@ -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); |