mirror of
https://repo.or.cz/socat.git
synced 2024-12-22 07:22:34 +00:00
Added socks5 client feature for connect and listen (experimental)
This commit is contained in:
parent
ebacb7c4e8
commit
7b66b53f93
10 changed files with 758 additions and 2 deletions
5
CHANGES
5
CHANGES
|
@ -122,6 +122,11 @@ Features:
|
|||
transfer. This can be used with "inetd mode" of systemd.
|
||||
Test: ACCEPT_FD
|
||||
|
||||
Added experimental socks5 TCP client support (connect,bind); syntax:
|
||||
SOCKS5-CONNECT:<socks-server>:<socks-port>:<target-host>:<target-port>
|
||||
SOCKS5-LISTEN:<socks-server>:<socks-port>:<listen-host>:<listen-port>
|
||||
Thanks to Charlie Svensson and others for contributions.
|
||||
|
||||
Corrections:
|
||||
When a sub process (EXEC, SYSTEM) terminated with exit code other than
|
||||
0, its last sent data might have been lost depending on timing of read/
|
||||
|
|
|
@ -49,7 +49,7 @@ XIOSRCS = xioinitialize.c xiohelp.c xioparam.c xiodiag.c xioopen.c xioopts.c \
|
|||
xio-socket.c xio-interface.c xio-listen.c xio-unix.c xio-vsock.c \
|
||||
xio-ip.c xio-ip4.c xio-ip6.c xio-ipapp.c xio-tcp.c \
|
||||
xio-sctp.c xio-rawip.c \
|
||||
xio-socks.c xio-proxy.c xio-udp.c \
|
||||
xio-socks.c xio-socks5.c xio-proxy.c xio-udp.c \
|
||||
xio-progcall.c xio-exec.c xio-system.c xio-termios.c xio-readline.c \
|
||||
xio-pty.c xio-openssl.c xio-streams.c xio-namespaces.c \
|
||||
xio-ascii.c xiolockfile.c xio-tcpwrap.c xio-fs.c xio-tun.c
|
||||
|
@ -67,7 +67,7 @@ HFILES = sycls.h sslcls.h error.h dalan.h procan.h filan.h hostan.h sysincludes.
|
|||
xio-socketpair.h xio-socket.h xio-interface.h xio-listen.h xio-unix.h xio-vsock.h \
|
||||
xio-ip.h xio-ip4.h xio-ip6.h xio-rawip.h \
|
||||
xio-ipapp.h xio-tcp.h xio-udp.h xio-sctp.h \
|
||||
xio-socks.h xio-proxy.h xio-progcall.h xio-exec.h \
|
||||
xio-socks.h xio-socks5.h xio-proxy.h xio-progcall.h xio-exec.h \
|
||||
xio-system.h xio-termios.h xio-readline.h \
|
||||
xio-pty.h xio-openssl.h xio-streams.h xio-namespaces.h \
|
||||
xio-ascii.h xiolockfile.h xio-tcpwrap.h xio-fs.h xio-tun.h
|
||||
|
|
|
@ -706,6 +706,7 @@
|
|||
#undef WITH_LISTEN
|
||||
#undef WITH_SOCKS4
|
||||
#undef WITH_SOCKS4A
|
||||
#undef WITH_SOCKS5
|
||||
#undef WITH_VSOCK
|
||||
#undef WITH_NAMESPACES
|
||||
#undef WITH_PROXY
|
||||
|
|
|
@ -460,6 +460,14 @@ AC_ARG_ENABLE(socks4a, [ --disable-socks4a disable socks4a support],
|
|||
esac],
|
||||
[AC_DEFINE(WITH_SOCKS4A) AC_MSG_RESULT(yes)])
|
||||
|
||||
AC_MSG_CHECKING(whether to include socks5 support)
|
||||
AC_ARG_ENABLE(socks5, [ --disable-socks5 disable socks5 support],
|
||||
[case "$enableval" in
|
||||
no) AC_MSG_RESULT(no);;
|
||||
*) AC_DEFINE(WITH_SOCKS5) AC_MSG_RESULT(yes);;
|
||||
esac],
|
||||
[AC_DEFINE(WITH_SOCKS5) AC_MSG_RESULT(yes)])
|
||||
|
||||
AC_MSG_CHECKING(whether to include proxy connect support)
|
||||
AC_ARG_ENABLE(proxy, [ --disable-proxy disable proxy connect support],
|
||||
[case "$enableval" in
|
||||
|
|
34
doc/socat.yo
34
doc/socat.yo
|
@ -971,13 +971,47 @@ label(ADDRESS_SOCKS4)dit(bf(tt(SOCKS4:<socks-server>:<host>:<port>)))
|
|||
link(pf)(OPTION_PROTOCOL_FAMILY),
|
||||
link(retry)(OPTION_RETRY)nl()
|
||||
See also:
|
||||
link(SOCKS5)(ADDRESS_SOCKS5_CONNECT),
|
||||
link(SOCKS4A)(ADDRESS_SOCKS4A),
|
||||
link(PROXY)(ADDRESS_PROXY_CONNECT),
|
||||
link(TCP)(ADDRESS_TCP_CONNECT)
|
||||
|
||||
label(ADDRESS_SOCKS4A)dit(bf(tt(SOCKS4A:<socks-server>:<host>:<port>)))
|
||||
like link(SOCKS4)(ADDRESS_SOCKS4), but uses socks protocol version 4a, thus
|
||||
leaving host name resolution to the socks server.nl()
|
||||
Option groups: link(FD)(GROUP_FD),link(SOCKET)(GROUP_SOCKET),link(IP4)(GROUP_IP4),link(IP6)(GROUP_IP6),link(TCP)(GROUP_TCP),link(SOCKS4)(GROUP_SOCKS),link(RETRY)(GROUP_RETRY) nl()
|
||||
|
||||
label(ADDRESS_SOCKS5_CONNECT)dit(bf(tt(SOCKS5-CONNECT:<socks-server>:<socks-port>:<target-host>:<target-port>)))
|
||||
Connects via <socks-server> [link(IP address)(TYPE_IP_ADDRESS)]
|
||||
to <target-host> [link(IPv4 address)(TYPE_IPV4_ADDRESS)]
|
||||
on <target-port> [link(TCP service)(TYPE_TCP_SERVICE)],
|
||||
using socks version 5 protocol over TCP. Currently no authentication mechanism is provided.nl()
|
||||
This address type is experimental.nl()
|
||||
Option groups: link(FD)(GROUP_FD), link(SOCKET)(GROUP_SOCKET), link(IP4)(GROUP_IP4), link(IP6)(GROUP_IP6), link(TCP)(GROUP_TCP), link(CHILD)(GROUP_CHILD), link(RETRY)(GROUP_RETRY)nl()
|
||||
Useful options:
|
||||
link(sourceport)(OPTION_SOURCEPORT),
|
||||
link(pf)(OPTION_PROTOCOL_FAMILY),
|
||||
link(retry)(OPTION_RETRY)nl()
|
||||
See also:
|
||||
link(SOCKS5-LISTEN)(ADDRESS_SOCKS5_LISTEN),
|
||||
link(SOCKS4)(ADDRESS_SOCKS4),
|
||||
link(SOCKS4A)(ADDRESS_SOCKS4A),
|
||||
link(PROXY)(ADDRESS_PROXY_CONNECT),
|
||||
link(TCP)(ADDRESS_TCP_CONNECT)
|
||||
|
||||
label(ADDRESS_SOCKS5_LISTEN)dit(bf(tt(SOCKS5-LISTEN:<socks-server>:<socks-port>:<listen-host>:<listen-port>)))
|
||||
Connects to <socks-server> [link(IP address)(TYPE_IP_ADDRESS)]
|
||||
using socks version 5 protocol over TCP
|
||||
and makes it listen for incoming connections on <listen-port> [link(TCP service)(TYPE_TCP_SERVICE)], binding to <-listen-host> [link(IPv4 address)(TYPE_IPV4_ADDRESS)]
|
||||
Currently not authentication mechanism is provided. This address type is experimental.
|
||||
Option groups: link(FD)(GROUP_FD), link(SOCKET)(GROUP_SOCKET), link(IP4)(GROUP_IP4), link(IP6)(GROUP_IP6), link(TCP)(GROUP_TCP), link(CHILD)(GROUP_CHILD), link(RETRY)(GROUP_RETRY)nl()
|
||||
Useful options:
|
||||
link(sourceport)(OPTION_SOURCEPORT),
|
||||
link(pf)(OPTION_PROTOCOL_FAMILY),
|
||||
link(retry)(OPTION_RETRY)nl()
|
||||
See also:
|
||||
link(SOCKS5-CONNECT)(ADDRESS_SOCKS5_CONNECT),
|
||||
|
||||
label(ADDRESS_STDERR)dit(bf(tt(STDERR)))
|
||||
Uses file descriptor 2.nl()
|
||||
Option groups: link(FD)(GROUP_FD) (link(TERMIOS)(GROUP_TERMIOS),link(REG)(GROUP_REG),link(SOCKET)(GROUP_SOCKET)) nl()
|
||||
|
|
5
socat.c
5
socat.c
|
@ -610,6 +610,11 @@ void socat_version(FILE *fd) {
|
|||
#else
|
||||
fputs(" #undef WITH_SOCKS4A\n", fd);
|
||||
#endif
|
||||
#ifdef WITH_SOCKS5
|
||||
fprintf(fd, " #define WITH_SOCKS5 %d\n", WITH_SOCKS5);
|
||||
#else
|
||||
fputs(" #undef WITH_SOCKS5\n", fd);
|
||||
#endif
|
||||
#ifdef WITH_VSOCK
|
||||
fprintf(fd, " #define WITH_VSOCK %d\n", WITH_VSOCK);
|
||||
#else
|
||||
|
|
655
xio-socks5.c
Normal file
655
xio-socks5.c
Normal file
|
@ -0,0 +1,655 @@
|
|||
/* source: xio-socks5.c */
|
||||
/* Copyright Gerhard Rieger and contributors (see file CHANGES) */
|
||||
/* Published under the GNU General Public License V.2, see file COPYING */
|
||||
|
||||
/* This file contains the source for opening addresses of socks5 type */
|
||||
|
||||
/*
|
||||
* At the moment UDP ASSOCIATE is not supported, but CONNECT and BIND are.
|
||||
* At the moment no authentication methods are supported (i.e only NO AUTH),
|
||||
* which is technically not compliant with RFC1928.
|
||||
*/
|
||||
|
||||
#include "xiosysincludes.h"
|
||||
|
||||
#if WITH_SOCKS5
|
||||
|
||||
#include "xioopen.h"
|
||||
#include "xio-ascii.h"
|
||||
#include "xio-socket.h"
|
||||
#include "xio-ip.h"
|
||||
#include "xio-ipapp.h"
|
||||
|
||||
#include "xio-socks5.h"
|
||||
|
||||
|
||||
#define SOCKS5_VERSION 5
|
||||
|
||||
#define SOCKS5_MAX_REPLY_SIZE (6 + 256)
|
||||
|
||||
#define SOCKS5_AUTH_NONE 0
|
||||
#define SOCKS5_AUTH_FAIL 0xff
|
||||
|
||||
#define SOCKS5_COMMAND_CONNECT 1
|
||||
#define SOCKS5_COMMAND_BIND 2
|
||||
#define SOCKS5_COMMAND_UDP_ASSOCIATE 3
|
||||
|
||||
#define SOCKS5_ATYPE_IPv4 1
|
||||
#define SOCKS5_ATYPE_DOMAINNAME 3
|
||||
#define SOCKS5_ATYPE_IPv6 4
|
||||
|
||||
#define SOCKS5_STATUS_SUCCESS 0
|
||||
#define SOCKS5_STATUS_GENERAL_FAILURE 1
|
||||
#define SOCKS5_STATUS_CONNECTION_NOT_ALLOWED 2
|
||||
#define SOCKS5_STATUS_NETWORK_UNREACHABLE 3
|
||||
#define SOCKS5_STATUS_HOST_UNREACHABLE 4
|
||||
#define SOCKS5_STATUS_CONNECTION_REFUSED 5
|
||||
#define SOCKS5_STATUS_TTL_EXPIRED 6
|
||||
#define SOCKS5_STATUS_COMMAND_NOT_SUPPORTED 7
|
||||
#define SOCKS5_STATUS_ADDRESS_TYPE_NOT_SUPPORTED 8
|
||||
|
||||
static int xioopen_socks5(int argc, const char *argv[], struct opt *opts,
|
||||
int xioflags, xiofile_t *xxfd,
|
||||
groups_t groups, int dummy1, int dummy2,
|
||||
int dummy3);
|
||||
|
||||
const struct addrdesc xioaddr_socks5_connect = { "SOCKS5-CONNECT", 1+XIO_RDWR, xioopen_socks5, GROUP_FD|GROUP_SOCKET|GROUP_SOCK_IP4|GROUP_SOCK_IP6|GROUP_IP_TCP|GROUP_CHILD|GROUP_RETRY, SOCKS5_COMMAND_CONNECT, 0, 0 HELP(":<socks-server>:<socks-port>:<target-host>:<target-port>") };
|
||||
|
||||
const struct addrdesc xioaddr_socks5_listen = { "SOCKS5-LISTEN", 1+XIO_RDWR, xioopen_socks5, GROUP_FD|GROUP_SOCKET|GROUP_SOCK_IP4|GROUP_SOCK_IP6|GROUP_IP_TCP|GROUP_CHILD|GROUP_RETRY, SOCKS5_COMMAND_BIND, 0, 0 HELP(":<socks-server>:<socks-port>:<listen-host>:<listen-port>") };
|
||||
|
||||
static const char * _xioopen_socks5_strerror(uint8_t r)
|
||||
{
|
||||
switch(r) {
|
||||
case SOCKS5_STATUS_SUCCESS:
|
||||
return "succeeded";
|
||||
case SOCKS5_STATUS_GENERAL_FAILURE:
|
||||
return "general SOCKS server failure";
|
||||
case SOCKS5_STATUS_CONNECTION_NOT_ALLOWED:
|
||||
return "connection not allowed by ruleset";
|
||||
case SOCKS5_STATUS_NETWORK_UNREACHABLE:
|
||||
return "network unreachable";
|
||||
case SOCKS5_STATUS_HOST_UNREACHABLE:
|
||||
return "host unreachable";
|
||||
case SOCKS5_STATUS_CONNECTION_REFUSED:
|
||||
return "connection refused";
|
||||
case SOCKS5_STATUS_TTL_EXPIRED:
|
||||
return "TTL expired";
|
||||
case SOCKS5_STATUS_COMMAND_NOT_SUPPORTED:
|
||||
return "command not supported";
|
||||
case SOCKS5_STATUS_ADDRESS_TYPE_NOT_SUPPORTED:
|
||||
return "address type not supported";
|
||||
default:
|
||||
return "unknown error";
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Performs the SOCKS5 handshake, i.e sends client hello and receives server
|
||||
* hello back.
|
||||
* If successful the connection is now ready for sending a SOCKS5 request.
|
||||
*
|
||||
* The code is unnecessarily complex right now, for what is essentially
|
||||
* send(0x050100) followed by "return read() == 0x0500", but will be easier to
|
||||
* extend for other auth mode support.
|
||||
*/
|
||||
static int _xioopen_socks5_handshake(struct single *xfd, int level)
|
||||
{
|
||||
int result;
|
||||
ssize_t bytes;
|
||||
struct socks5_server_hello server_hello;
|
||||
int nmethods = 1; /* support only 1 auth method - no auth */
|
||||
int client_hello_size =
|
||||
sizeof(struct socks5_client_hello) +
|
||||
(sizeof(uint8_t) * nmethods);
|
||||
|
||||
struct socks5_client_hello *client_hello = Malloc(client_hello_size);
|
||||
if (client_hello == NULL) {
|
||||
Msg2(level, "malloc(%d): %s",
|
||||
client_hello_size, strerror(errno));
|
||||
if (Close(xfd->fd) < 0) {
|
||||
Info2("close(%d): %s", xfd->fd, strerror(errno));
|
||||
}
|
||||
|
||||
/* malloc failed - could succeed later, so retry then */
|
||||
return STAT_RETRYLATER;
|
||||
}
|
||||
|
||||
unsigned char *server_hello_ptr = (unsigned char *)&server_hello;
|
||||
|
||||
/* SOCKS5 Hello with 1 authentication mechanism -
|
||||
0x00 NO AUTHENTICATION */
|
||||
client_hello->version = SOCKS5_VERSION;
|
||||
client_hello->nmethods = 1;
|
||||
client_hello->methods[0]= SOCKS5_AUTH_NONE;
|
||||
|
||||
/* Send SOCKS5 Client Hello */
|
||||
Info2("sending socks5 client hello version=%d nmethods=%d",
|
||||
client_hello->version,
|
||||
client_hello->nmethods);
|
||||
#if WITH_MSGLEVEL <= E_DEBUG
|
||||
{
|
||||
char *msgbuf;
|
||||
if ((msgbuf = Malloc(3 * client_hello_size)) != NULL) {
|
||||
xiohexdump((unsigned char *)client_hello,
|
||||
client_hello_size, msgbuf);
|
||||
Debug1("sending socks5 client hello %s", msgbuf);
|
||||
free(msgbuf);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (writefull(xfd->fd, client_hello, client_hello_size) < 0) {
|
||||
Msg4(level, "write(%d, %p, %d): %s",
|
||||
xfd->fd, client_hello, client_hello_size,
|
||||
strerror(errno));
|
||||
if (Close(xfd->fd) < 0) {
|
||||
Info2("close(%d): %s", xfd->fd, strerror(errno));
|
||||
}
|
||||
free(client_hello);
|
||||
|
||||
/* writefull() failed, but might succeed later, so RETRYLATER */
|
||||
return STAT_RETRYLATER;
|
||||
}
|
||||
free(client_hello);
|
||||
|
||||
bytes = 0;
|
||||
Info("waiting for socks5 reply");
|
||||
while (bytes >= 0) {
|
||||
do {
|
||||
result = Read(xfd->fd, server_hello_ptr + bytes,
|
||||
sizeof(struct socks5_server_hello)-bytes);
|
||||
} while (result < 0 && errno == EINTR);
|
||||
if (result < 0) {
|
||||
Msg4(level, "read(%d, %p, "F_Zu"): %s",
|
||||
xfd->fd, server_hello_ptr + bytes,
|
||||
sizeof(struct socks5_server_hello)-bytes,
|
||||
strerror(errno));
|
||||
if (Close(xfd->fd) < 0) {
|
||||
Info2("close(%d): %s", xfd->fd, strerror(errno));
|
||||
}
|
||||
}
|
||||
if (result == 0) {
|
||||
Msg(level, "read(): EOF during read of SOCKS5 server hello, peer might not be a SOCKS5 server");
|
||||
if (Close(xfd->fd) < 0) {
|
||||
Info2("close(%d): %s", xfd->fd,
|
||||
strerror(errno));
|
||||
}
|
||||
|
||||
return STAT_RETRYLATER;
|
||||
}
|
||||
|
||||
bytes += result;
|
||||
if (bytes == sizeof(struct socks5_server_hello)) {
|
||||
Debug1("received all "F_Zd" bytes", bytes);
|
||||
break;
|
||||
}
|
||||
Debug2("received %d bytes, waiting for "F_Zu" more bytes",
|
||||
result, sizeof(struct socks5_server_hello)-bytes);
|
||||
}
|
||||
if (result <= 0) {
|
||||
return STAT_RETRYLATER;
|
||||
}
|
||||
|
||||
Info2("received SOCKS5 server hello version=%d method=%d",
|
||||
server_hello.version,
|
||||
server_hello.method);
|
||||
|
||||
if (server_hello.version != SOCKS5_VERSION) {
|
||||
Msg2(level, "SOCKS5 Server Hello version was %d, not the expected %d, peer might not be a SOCKS5 server",
|
||||
server_hello.version, SOCKS5_VERSION);
|
||||
if (Close(xfd->fd) < 0) {
|
||||
Info2("close(%d): %s", xfd->fd, strerror(errno));
|
||||
}
|
||||
return STAT_RETRYLATER;
|
||||
}
|
||||
|
||||
if (server_hello.method == SOCKS5_AUTH_FAIL) {
|
||||
Msg(level, "SOCKS5 authentication negotiation failed - client & server have no common supported methods");
|
||||
if (Close(xfd->fd) < 0) {
|
||||
Info2("close(%d): %s", xfd->fd, strerror(errno));
|
||||
}
|
||||
return STAT_RETRYLATER;
|
||||
}
|
||||
|
||||
if (server_hello.method != SOCKS5_AUTH_NONE) {
|
||||
Msg1(level, "SOCKS5 server requested unsupported auth method (%d)",
|
||||
server_hello.method);
|
||||
if (Close(xfd->fd) < 0) {
|
||||
Info2("close(%d): %s", xfd->fd, strerror(errno));
|
||||
}
|
||||
return STAT_RETRYLATER;
|
||||
}
|
||||
|
||||
/* Server accepted using no auth */
|
||||
return STAT_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* Generates the SOCKS5 request for a given command, host and port
|
||||
*/
|
||||
static struct socks5_request *_xioopen_socks5_prepare_request(
|
||||
int *bytes, const char *target_name, const char *target_port,
|
||||
uint8_t socks_command, int level)
|
||||
{
|
||||
struct socks5_request *req;
|
||||
char ipaddr[16];
|
||||
uint16_t *dstport;
|
||||
|
||||
*bytes = 0;
|
||||
|
||||
if (inet_pton(AF_INET, target_name, ipaddr)){ /* if(valid_ipv4) */
|
||||
*bytes = sizeof(struct socks5_request) + 4 + sizeof(uint16_t);
|
||||
req = (struct socks5_request *)Malloc(*bytes);
|
||||
if (req == NULL){
|
||||
Info2("Malloc(%d): %s", *bytes, strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
req->address_type = SOCKS5_ATYPE_IPv4;
|
||||
memcpy(req->dstdata, ipaddr, 4);
|
||||
|
||||
dstport = (uint16_t *) &req->dstdata[4];
|
||||
*dstport = parseport(target_port, IPPROTO_TCP);
|
||||
} else if (inet_pton(AF_INET6, target_name, ipaddr)) { /* else if(valid_ipv6) */
|
||||
*bytes = sizeof(struct socks5_request) + 16 + sizeof(uint16_t);
|
||||
req = (struct socks5_request *)Malloc(*bytes);
|
||||
if (req == NULL){
|
||||
Info2("Malloc(%d): %s", *bytes, strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
req->address_type = SOCKS5_ATYPE_IPv6;
|
||||
memcpy(req->dstdata, ipaddr, 16);
|
||||
|
||||
dstport = (uint16_t *) &req->dstdata[16];
|
||||
*dstport = parseport(target_port, IPPROTO_TCP);
|
||||
} else { /* invalid IP, assume hostname */
|
||||
int hlen = strlen(target_name);
|
||||
if (hlen > 255) {
|
||||
Msg(level, "target hostname too long (>255 bytes), aborting");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*bytes = sizeof(struct socks5_request) + 1 + hlen +
|
||||
sizeof(uint16_t);
|
||||
req = (struct socks5_request *)Malloc(*bytes);
|
||||
if (req == NULL ){
|
||||
Info2("malloc(%d): %s", *bytes, strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
req->address_type = SOCKS5_ATYPE_DOMAINNAME;
|
||||
req->dstdata[0] = (unsigned char) hlen;
|
||||
memcpy(&req->dstdata[1], target_name, hlen);
|
||||
|
||||
dstport = (uint16_t *) &req->dstdata[hlen + 1];
|
||||
*dstport = parseport(target_port, IPPROTO_TCP);
|
||||
}
|
||||
|
||||
|
||||
if (*dstport == 0){
|
||||
free(req);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
req->version = SOCKS5_VERSION;
|
||||
req->command = socks_command;
|
||||
req->reserved = 0;
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
/*
|
||||
* Reads a server reply after a request has been sent
|
||||
*/
|
||||
static int _xioopen_socks5_read_reply(
|
||||
struct single *xfd, struct socks5_reply *reply, int level)
|
||||
{
|
||||
int result = 0;
|
||||
int bytes_read = 0;
|
||||
int bytes_to_read = 5;
|
||||
bool typechecked = false;
|
||||
|
||||
while (bytes_to_read >= 0) {
|
||||
Info("reading SOCKS5 reply");
|
||||
do {
|
||||
result = Read(xfd->fd,
|
||||
((unsigned char *)reply) + bytes_read,
|
||||
bytes_to_read-bytes_read);
|
||||
} while (result < 0 && errno == EINTR);
|
||||
if (result < 0) {
|
||||
Msg4(level, "read(%d, %p, %d): %s",
|
||||
xfd->fd, ((unsigned char *)reply) + bytes_read,
|
||||
bytes_to_read-bytes_read, strerror(errno));
|
||||
if (Close(xfd->fd) < 0) {
|
||||
Info2("close(%d): %s", xfd->fd, strerror(errno));
|
||||
}
|
||||
return STAT_RETRYLATER;
|
||||
}
|
||||
if (result == 0) {
|
||||
Msg(level, "read(): EOF during read of SOCKS5 reply");
|
||||
if (Close(xfd->fd) < 0) {
|
||||
Info2("close(%d): %s",
|
||||
xfd->fd, strerror(errno));
|
||||
}
|
||||
return STAT_RETRYLATER;
|
||||
}
|
||||
bytes_read += result;
|
||||
|
||||
/* Once we've read 5 bytes, figure out total message length and
|
||||
* update bytes_to_read accordingly. */
|
||||
if (!typechecked && bytes_read <= 5) {
|
||||
switch(reply->address_type) {
|
||||
case SOCKS5_ATYPE_IPv4:
|
||||
/* 6 fixed bytes, and 4 bytes for v4 address */
|
||||
bytes_to_read = 10;
|
||||
break;
|
||||
case SOCKS5_ATYPE_IPv6:
|
||||
/* 6 fixed bytes, and 16 bytes for v6 address */
|
||||
bytes_to_read = 22;
|
||||
break;
|
||||
case SOCKS5_ATYPE_DOMAINNAME:
|
||||
/* 6 fixed bytes, 1 byte for strlen,
|
||||
and 0-255 bytes for domain name */
|
||||
bytes_to_read = 7 + reply->dstdata[0];
|
||||
break;
|
||||
default:
|
||||
Msg1(level, "invalid SOCKS5 reply address type (%d)",
|
||||
reply->address_type);
|
||||
if (Close(xfd->fd) < 0) {
|
||||
Info2("close(%d): %s",
|
||||
xfd->fd, strerror(errno));
|
||||
}
|
||||
return STAT_RETRYLATER;
|
||||
}
|
||||
typechecked = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (bytes_to_read == bytes_read) {
|
||||
Debug1("received all %d bytes", bytes_read);
|
||||
break;
|
||||
}
|
||||
|
||||
Debug2("received %d of %d bytes, waiting",
|
||||
bytes_read, bytes_to_read);
|
||||
}
|
||||
|
||||
if (result <= 0) {
|
||||
return STAT_RETRYLATER;
|
||||
}
|
||||
|
||||
return STAT_OK;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Sends a request and receives the reply.
|
||||
* If command is BIND we receive two replies.
|
||||
*/
|
||||
static int _xioopen_socks5_request(
|
||||
struct single *xfd, const char *target_name, const char *target_port,
|
||||
uint8_t socks_command, int level)
|
||||
{
|
||||
struct socks5_request *req;
|
||||
int bytes, result = 0;
|
||||
|
||||
req = _xioopen_socks5_prepare_request(&bytes, target_name, target_port,
|
||||
socks_command, level);
|
||||
if (req == NULL) {
|
||||
if (Close(xfd->fd) < 0) {
|
||||
Info2("close(%d): %s", xfd->fd, strerror(errno));
|
||||
}
|
||||
|
||||
/* Prepare_request could fail due to malloc, but most likely
|
||||
the destination is invalid, e.g too long hostname, so NORETRY */
|
||||
return STAT_NORETRY;
|
||||
}
|
||||
|
||||
Info4("sending socks5 request version=%d command=%d reserved=%d address_type=%d",
|
||||
req->version, req->command, req->reserved, req->address_type);
|
||||
|
||||
#if WITH_MSGLEVEL <= E_DEBUG
|
||||
{
|
||||
char *msgbuf;
|
||||
if ((msgbuf = Malloc(3 * bytes)) != NULL) {
|
||||
xiohexdump((const unsigned char *)req, bytes, msgbuf);
|
||||
Debug1("sending socks5 request %s", msgbuf);
|
||||
free(msgbuf);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (writefull(xfd->fd, req, bytes) < 0) {
|
||||
Msg4(level, "write(%d, %p, %d): %s",
|
||||
xfd->fd, req, bytes, strerror(errno));
|
||||
if (Close(xfd->fd) < 0) {
|
||||
Info2("close(%d): %s", xfd->fd, strerror(errno));
|
||||
}
|
||||
free(req);
|
||||
return STAT_RETRYLATER;
|
||||
}
|
||||
free(req);
|
||||
req = NULL;
|
||||
|
||||
struct socks5_reply *reply = Malloc(SOCKS5_MAX_REPLY_SIZE);
|
||||
if (reply == NULL) {
|
||||
if (Close(xfd->fd) < 0) {
|
||||
Info2("close(%d): %s", xfd->fd, strerror(errno));
|
||||
}
|
||||
|
||||
return STAT_RETRYLATER;
|
||||
}
|
||||
|
||||
result = _xioopen_socks5_read_reply(xfd, reply, level);
|
||||
if (result != STAT_OK) {
|
||||
free(reply);
|
||||
return result;
|
||||
}
|
||||
|
||||
/* TODO: maybe output nicer debug, like including address */
|
||||
Info3("received SOCKS5 reply version=%d reply=%d address_type=%d",
|
||||
reply->version, reply->reply, reply->address_type);
|
||||
|
||||
if (reply->version != SOCKS5_VERSION) {
|
||||
Msg2(level, "SOCKS5 reply version was %d, not the expected %d, peer might not be a SOCKS5 server",
|
||||
reply->version, SOCKS5_VERSION);
|
||||
if (Close(xfd->fd) < 0) {
|
||||
Info2("close(%d): %s", xfd->fd, strerror(errno));
|
||||
}
|
||||
free(reply);
|
||||
return STAT_RETRYLATER;
|
||||
}
|
||||
|
||||
if (reply->reply == SOCKS5_STATUS_SUCCESS &&
|
||||
socks_command == SOCKS5_COMMAND_BIND) {
|
||||
Notice("listening on remote host, waiting for connection");
|
||||
/* TODO: nicer debug output */
|
||||
/* For BIND, we read two replies */
|
||||
result = _xioopen_socks5_read_reply(xfd, reply, level);
|
||||
if (result != STAT_OK) {
|
||||
free(reply);
|
||||
return result;
|
||||
}
|
||||
Notice("received connection on remote host");
|
||||
/* TODO: maybe output nicer debug, like including address */
|
||||
Info3("received second SOCKS5 reply version=%d reply=%d address_type=%d",
|
||||
reply->version, reply->reply, reply->address_type);
|
||||
}
|
||||
|
||||
switch (reply->reply) {
|
||||
case SOCKS5_STATUS_SUCCESS:
|
||||
break;
|
||||
default:
|
||||
Msg2(level, "SOCKS5 server error %d: %s",
|
||||
reply->reply,
|
||||
_xioopen_socks5_strerror(reply->reply));
|
||||
if (Close(xfd->fd) < 0) {
|
||||
Info2("close(%d): %s", xfd->fd, strerror(errno));
|
||||
}
|
||||
free(reply);
|
||||
return STAT_RETRYLATER;
|
||||
}
|
||||
|
||||
free(reply);
|
||||
return STAT_OK;
|
||||
}
|
||||
|
||||
/* Same function for all socks5-modes, determined by argv[0] */
|
||||
static int xioopen_socks5(
|
||||
int argc, const char *argv[], struct opt *opts, int xioflags,
|
||||
xiofile_t *xxfd, groups_t groups, int socks_command, int dummy2,
|
||||
int dummy3)
|
||||
{
|
||||
bool dofork = false;
|
||||
int socktype = SOCK_STREAM;
|
||||
int pf = PF_UNSPEC;
|
||||
int ipproto = IPPROTO_TCP;
|
||||
int level, result;
|
||||
struct opt *opts0 = NULL;
|
||||
struct single *xfd = &xxfd->stream;
|
||||
const char *socks_server, *target_name, *target_port, *socks_port;
|
||||
union sockaddr_union us_sa, *us = &us_sa;
|
||||
socklen_t uslen = sizeof(us_sa);
|
||||
struct addrinfo *themlist, *themp;
|
||||
bool needbind = false;
|
||||
bool lowport = false;
|
||||
char infobuff[256];
|
||||
|
||||
if (!xioparms.experimental) {
|
||||
Error1("%s: requires option --experimental", argv[0]);
|
||||
return STAT_NORETRY;
|
||||
}
|
||||
if (argc != 5) {
|
||||
Error1("%s: 4 parameters required like :<socks-server>:<socks-port>:<target-server>:<target-port>",
|
||||
argv[0]);
|
||||
return STAT_NORETRY;
|
||||
}
|
||||
|
||||
socks_server = argv[1];
|
||||
socks_port = argv[2];
|
||||
target_name = argv[3];
|
||||
target_port = argv[4];
|
||||
|
||||
xfd->howtoend = END_SHUTDOWN;
|
||||
if (applyopts_single(xfd, opts, PH_INIT) < 0) return -1;
|
||||
applyopts(-1, opts, PH_INIT);
|
||||
|
||||
retropt_int(opts, OPT_SO_TYPE, &socktype);
|
||||
retropt_bool(opts, OPT_FORK, &dofork);
|
||||
|
||||
result = _xioopen_ipapp_prepare(opts, &opts0, socks_server, socks_port,
|
||||
&pf, ipproto,
|
||||
xfd->para.socket.ip.ai_flags,
|
||||
&themlist, us, &uslen,
|
||||
&needbind, &lowport, socktype);
|
||||
|
||||
Notice2("connecting to socks5 server %s:%s",
|
||||
socks_server, socks_port);
|
||||
|
||||
do {
|
||||
#if WITH_RETRY
|
||||
if (xfd->forever || xfd->retry) {
|
||||
level = E_INFO;
|
||||
} else {
|
||||
level = E_ERROR;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* loop over themlist */
|
||||
themp = themlist;
|
||||
while (themp != NULL) {
|
||||
Notice1("opening connection to %s",
|
||||
sockaddr_info(themp->ai_addr, themp->ai_addrlen,
|
||||
infobuff, sizeof(infobuff)));
|
||||
result = _xioopen_connect(xfd, needbind?us:NULL, sizeof(*us),
|
||||
themp->ai_addr, themp->ai_addrlen,
|
||||
opts, pf?pf:themp->ai_family, socktype,
|
||||
IPPROTO_TCP, lowport, level);
|
||||
if (result == STAT_OK)
|
||||
break;
|
||||
themp = themp->ai_next;
|
||||
if (themp == NULL)
|
||||
result = STAT_RETRYLATER;
|
||||
|
||||
switch(result){
|
||||
break;
|
||||
#if WITH_RETRY
|
||||
case STAT_RETRYLATER:
|
||||
case STAT_RETRYNOW:
|
||||
if (xfd->forever || xfd->retry-- ) {
|
||||
if (result == STAT_RETRYLATER) Nanosleep(&xfd->intervall, NULL);
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
default:
|
||||
xiofreeaddrinfo(themlist);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
xiofreeaddrinfo(themlist);
|
||||
applyopts(xfd->fd, opts, PH_ALL);
|
||||
|
||||
if ((result = _xio_openlate(xfd, opts)) < 0)
|
||||
return result;
|
||||
|
||||
if ((result = _xioopen_socks5_handshake(xfd, level)) != STAT_OK) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result = _xioopen_socks5_request(xfd, target_name, target_port, socks_command, level);
|
||||
switch (result) {
|
||||
case STAT_OK:
|
||||
break;
|
||||
#if WITH_RETRY
|
||||
case STAT_RETRYLATER:
|
||||
case STAT_RETRYNOW:
|
||||
if ( xfd->forever || xfd->retry-- ) {
|
||||
if (result == STAT_RETRYLATER) Nanosleep(&xfd->intervall, NULL);
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
default:
|
||||
return result;
|
||||
}
|
||||
|
||||
if (dofork) {
|
||||
xiosetchilddied();
|
||||
}
|
||||
|
||||
#if WITH_RETRY
|
||||
if (dofork) {
|
||||
pid_t pid;
|
||||
int level = E_ERROR;
|
||||
if (xfd->forever || xfd->retry) {
|
||||
level = E_WARN;
|
||||
}
|
||||
while ((pid = xio_fork(false, level, xfd->shutup)) < 0) {
|
||||
if (xfd->forever || --xfd->retry) {
|
||||
Nanosleep(&xfd->intervall, NULL);
|
||||
continue;
|
||||
}
|
||||
return STAT_RETRYLATER;
|
||||
}
|
||||
if ( pid == 0 ) {
|
||||
xfd->forever = false;
|
||||
xfd->retry = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
Close(xfd->fd);
|
||||
Nanosleep(&xfd->intervall, NULL);
|
||||
dropopts(opts, PH_ALL);
|
||||
opts = copyopts(opts0, GROUP_ALL);
|
||||
continue;
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
break;
|
||||
}
|
||||
} while (true);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* WITH_SOCKS5 */
|
41
xio-socks5.h
Normal file
41
xio-socks5.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
/* source: xio-socks5.h */
|
||||
/* Copyright Gerhard Rieger and contributors (see file CHANGES) */
|
||||
/* Published under the GNU General Public License V.2, see file COPYING */
|
||||
|
||||
#ifndef __xio_socks5_h_included
|
||||
#define __xio_socks5_h_included 1
|
||||
|
||||
#if WITH_SOCKS5
|
||||
|
||||
struct socks5_client_hello {
|
||||
uint8_t version;
|
||||
uint8_t nmethods;
|
||||
uint8_t methods[];
|
||||
};
|
||||
|
||||
struct socks5_server_hello {
|
||||
uint8_t version;
|
||||
uint8_t method;
|
||||
};
|
||||
|
||||
struct socks5_request {
|
||||
uint8_t version;
|
||||
uint8_t command;
|
||||
uint8_t reserved;
|
||||
uint8_t address_type;
|
||||
char dstdata[];
|
||||
};
|
||||
|
||||
struct socks5_reply {
|
||||
uint8_t version;
|
||||
uint8_t reply;
|
||||
uint8_t reserved;
|
||||
uint8_t address_type;
|
||||
char dstdata[];
|
||||
};
|
||||
|
||||
extern const struct addrdesc xioaddr_socks5_connect;
|
||||
extern const struct addrdesc xioaddr_socks5_listen;
|
||||
|
||||
#endif /* WITH_SOCKS5 */
|
||||
#endif /* !defined(__xio_socks5_h_included) */
|
|
@ -32,6 +32,7 @@
|
|||
#include "xio-udp.h"
|
||||
#include "xio-sctp.h"
|
||||
#include "xio-socks.h"
|
||||
#include "xio-socks5.h"
|
||||
#include "xio-proxy.h"
|
||||
#include "xio-vsock.h"
|
||||
#endif /* _WITH_SOCKET */
|
||||
|
|
|
@ -207,6 +207,12 @@ const struct addrname addressnames[] = {
|
|||
#if WITH_SOCKS4A
|
||||
{ "SOCKS4A", &xioaddr_socks4a_connect },
|
||||
#endif
|
||||
#if WITH_SOCKS5
|
||||
{ "SOCKS5", &xioaddr_socks5_connect },
|
||||
{ "SOCKS5-BIND", &xioaddr_socks5_listen },
|
||||
{ "SOCKS5-CONNECT", &xioaddr_socks5_connect },
|
||||
{ "SOCKS5-LISTEN", &xioaddr_socks5_listen },
|
||||
#endif
|
||||
#if WITH_OPENSSL
|
||||
{ "SSL", &xioaddr_openssl },
|
||||
#if WITH_LISTEN
|
||||
|
|
Loading…
Reference in a new issue