Move the SOCKS code to common.c
This commit is contained in:
parent
d2e62bc80d
commit
4d4bdc1e6a
407
common.c
407
common.c
|
@ -29,6 +29,7 @@
|
||||||
#define print_debug_data ((void *) LOG_DEBUG)
|
#define print_debug_data ((void *) LOG_DEBUG)
|
||||||
|
|
||||||
#include "liberty/liberty.c"
|
#include "liberty/liberty.c"
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
|
||||||
// --- Logging -----------------------------------------------------------------
|
// --- Logging -----------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -49,3 +50,409 @@ log_message_syslog (void *user_data, const char *quote, const char *fmt,
|
||||||
if (vsnprintf (buf, sizeof buf, fmt, ap) >= 0)
|
if (vsnprintf (buf, sizeof buf, fmt, ap) >= 0)
|
||||||
syslog (prio, "%s%s", quote, buf);
|
syslog (prio, "%s%s", quote, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- SOCKS 5/4a (blocking implementation) ------------------------------------
|
||||||
|
|
||||||
|
// These are awkward protocols. Note that the `username' is used differently
|
||||||
|
// in SOCKS 4a and 5. In the former version, it is the username that you can
|
||||||
|
// get ident'ed against. In the latter version, it forms a pair with the
|
||||||
|
// password field and doesn't need to be an actual user on your machine.
|
||||||
|
|
||||||
|
// TODO: make a non-blocking poller-based version of this;
|
||||||
|
// either use c-ares or (even better) start another thread to do resolution
|
||||||
|
|
||||||
|
struct socks_addr
|
||||||
|
{
|
||||||
|
enum socks_addr_type
|
||||||
|
{
|
||||||
|
SOCKS_IPV4 = 1, ///< IPv4 address
|
||||||
|
SOCKS_DOMAIN = 3, ///< Domain name to be resolved
|
||||||
|
SOCKS_IPV6 = 4 ///< IPv6 address
|
||||||
|
}
|
||||||
|
type; ///< The type of this address
|
||||||
|
union
|
||||||
|
{
|
||||||
|
uint8_t ipv4[4]; ///< IPv4 address, network octet order
|
||||||
|
const char *domain; ///< Domain name
|
||||||
|
uint8_t ipv6[16]; ///< IPv6 address, network octet order
|
||||||
|
}
|
||||||
|
data; ///< The address itself
|
||||||
|
};
|
||||||
|
|
||||||
|
struct socks_data
|
||||||
|
{
|
||||||
|
struct socks_addr address; ///< Target address
|
||||||
|
uint16_t port; ///< Target port
|
||||||
|
const char *username; ///< Authentication username
|
||||||
|
const char *password; ///< Authentication password
|
||||||
|
|
||||||
|
struct socks_addr bound_address; ///< Bound address at the server
|
||||||
|
uint16_t bound_port; ///< Bound port at the server
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool
|
||||||
|
socks_get_socket (struct addrinfo *addresses, int *fd, struct error **e)
|
||||||
|
{
|
||||||
|
int sockfd;
|
||||||
|
for (; addresses; addresses = addresses->ai_next)
|
||||||
|
{
|
||||||
|
sockfd = socket (addresses->ai_family,
|
||||||
|
addresses->ai_socktype, addresses->ai_protocol);
|
||||||
|
if (sockfd == -1)
|
||||||
|
continue;
|
||||||
|
set_cloexec (sockfd);
|
||||||
|
|
||||||
|
int yes = 1;
|
||||||
|
soft_assert (setsockopt (sockfd, SOL_SOCKET, SO_KEEPALIVE,
|
||||||
|
&yes, sizeof yes) != -1);
|
||||||
|
|
||||||
|
if (!connect (sockfd, addresses->ai_addr, addresses->ai_addrlen))
|
||||||
|
break;
|
||||||
|
xclose (sockfd);
|
||||||
|
}
|
||||||
|
if (!addresses)
|
||||||
|
{
|
||||||
|
error_set (e, "couldn't connect to the SOCKS server");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*fd = sockfd;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define SOCKS_FAIL(...) \
|
||||||
|
BLOCK_START \
|
||||||
|
error_set (e, __VA_ARGS__); \
|
||||||
|
goto fail; \
|
||||||
|
BLOCK_END
|
||||||
|
#define SOCKS_RECV(buf, len) \
|
||||||
|
BLOCK_START \
|
||||||
|
if ((n = recv (sockfd, (buf), (len), 0)) == -1) \
|
||||||
|
SOCKS_FAIL ("%s: %s", "recv", strerror (errno)); \
|
||||||
|
if (n != (len)) \
|
||||||
|
SOCKS_FAIL ("%s: %s", "protocol error", "unexpected EOF"); \
|
||||||
|
BLOCK_END
|
||||||
|
|
||||||
|
static bool
|
||||||
|
socks_4a_connect (struct addrinfo *addresses, struct socks_data *data,
|
||||||
|
int *fd, struct error **e)
|
||||||
|
{
|
||||||
|
int sockfd;
|
||||||
|
if (!socks_get_socket (addresses, &sockfd, e))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const void *dest_ipv4 = "\x00\x00\x00\x01";
|
||||||
|
const char *dest_domain = NULL;
|
||||||
|
|
||||||
|
char buf[INET6_ADDRSTRLEN];
|
||||||
|
switch (data->address.type)
|
||||||
|
{
|
||||||
|
case SOCKS_IPV4:
|
||||||
|
dest_ipv4 = data->address.data.ipv4;
|
||||||
|
break;
|
||||||
|
case SOCKS_IPV6:
|
||||||
|
// About the best thing we can do, not sure if it works anywhere at all
|
||||||
|
if (!inet_ntop (AF_INET6, &data->address.data.ipv6, buf, sizeof buf))
|
||||||
|
SOCKS_FAIL ("%s: %s", "inet_ntop", strerror (errno));
|
||||||
|
dest_domain = buf;
|
||||||
|
break;
|
||||||
|
case SOCKS_DOMAIN:
|
||||||
|
dest_domain = data->address.data.domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct str req;
|
||||||
|
str_init (&req);
|
||||||
|
str_append_c (&req, 4); // version
|
||||||
|
str_append_c (&req, 1); // connect
|
||||||
|
|
||||||
|
str_append_c (&req, data->port >> 8); // higher bits of port
|
||||||
|
str_append_c (&req, data->port); // lower bits of port
|
||||||
|
str_append_data (&req, dest_ipv4, 4); // destination address
|
||||||
|
|
||||||
|
if (data->username)
|
||||||
|
str_append (&req, data->username);
|
||||||
|
str_append_c (&req, '\0');
|
||||||
|
|
||||||
|
if (dest_domain)
|
||||||
|
{
|
||||||
|
str_append (&req, dest_domain);
|
||||||
|
str_append_c (&req, '\0');
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t n = send (sockfd, req.str, req.len, 0);
|
||||||
|
str_free (&req);
|
||||||
|
if (n == -1)
|
||||||
|
SOCKS_FAIL ("%s: %s", "send", strerror (errno));
|
||||||
|
|
||||||
|
uint8_t resp[8];
|
||||||
|
SOCKS_RECV (resp, sizeof resp);
|
||||||
|
if (resp[0] != 0)
|
||||||
|
SOCKS_FAIL ("protocol error");
|
||||||
|
|
||||||
|
switch (resp[1])
|
||||||
|
{
|
||||||
|
case 90:
|
||||||
|
break;
|
||||||
|
case 91:
|
||||||
|
SOCKS_FAIL ("request rejected or failed");
|
||||||
|
case 92:
|
||||||
|
SOCKS_FAIL ("%s: %s", "request rejected",
|
||||||
|
"SOCKS server cannot connect to identd on the client");
|
||||||
|
case 93:
|
||||||
|
SOCKS_FAIL ("%s: %s", "request rejected",
|
||||||
|
"identd reports different user-id");
|
||||||
|
default:
|
||||||
|
SOCKS_FAIL ("protocol error");
|
||||||
|
}
|
||||||
|
|
||||||
|
*fd = sockfd;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
xclose (sockfd);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef SOCKS_FAIL
|
||||||
|
#define SOCKS_FAIL(...) \
|
||||||
|
BLOCK_START \
|
||||||
|
error_set (e, __VA_ARGS__); \
|
||||||
|
return false; \
|
||||||
|
BLOCK_END
|
||||||
|
|
||||||
|
static bool
|
||||||
|
socks_5_userpass_auth (int sockfd, struct socks_data *data, struct error **e)
|
||||||
|
{
|
||||||
|
size_t ulen = strlen (data->username);
|
||||||
|
if (ulen > 255)
|
||||||
|
ulen = 255;
|
||||||
|
|
||||||
|
size_t plen = strlen (data->password);
|
||||||
|
if (plen > 255)
|
||||||
|
plen = 255;
|
||||||
|
|
||||||
|
uint8_t req[3 + ulen + plen], *p = req;
|
||||||
|
*p++ = 0x01; // version
|
||||||
|
*p++ = ulen; // username length
|
||||||
|
memcpy (p, data->username, ulen);
|
||||||
|
p += ulen;
|
||||||
|
*p++ = plen; // password length
|
||||||
|
memcpy (p, data->password, plen);
|
||||||
|
p += plen;
|
||||||
|
|
||||||
|
ssize_t n = send (sockfd, req, p - req, 0);
|
||||||
|
if (n == -1)
|
||||||
|
SOCKS_FAIL ("%s: %s", "send", strerror (errno));
|
||||||
|
|
||||||
|
uint8_t resp[2];
|
||||||
|
SOCKS_RECV (resp, sizeof resp);
|
||||||
|
if (resp[0] != 0x01)
|
||||||
|
SOCKS_FAIL ("protocol error");
|
||||||
|
if (resp[1] != 0x00)
|
||||||
|
SOCKS_FAIL ("authentication failure");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
socks_5_auth (int sockfd, struct socks_data *data, struct error **e)
|
||||||
|
{
|
||||||
|
bool can_auth = data->username && data->password;
|
||||||
|
|
||||||
|
uint8_t hello[4];
|
||||||
|
hello[0] = 0x05; // version
|
||||||
|
hello[1] = 1 + can_auth; // number of authentication methods
|
||||||
|
hello[2] = 0x00; // no authentication required
|
||||||
|
hello[3] = 0x02; // username/password
|
||||||
|
|
||||||
|
ssize_t n = send (sockfd, hello, 3 + can_auth, 0);
|
||||||
|
if (n == -1)
|
||||||
|
SOCKS_FAIL ("%s: %s", "send", strerror (errno));
|
||||||
|
|
||||||
|
uint8_t resp[2];
|
||||||
|
SOCKS_RECV (resp, sizeof resp);
|
||||||
|
if (resp[0] != 0x05)
|
||||||
|
SOCKS_FAIL ("protocol error");
|
||||||
|
|
||||||
|
switch (resp[1])
|
||||||
|
{
|
||||||
|
case 0x02:
|
||||||
|
if (!can_auth)
|
||||||
|
SOCKS_FAIL ("protocol error");
|
||||||
|
if (!socks_5_userpass_auth (sockfd, data, e))
|
||||||
|
return false;
|
||||||
|
case 0x00:
|
||||||
|
break;
|
||||||
|
case 0xFF:
|
||||||
|
SOCKS_FAIL ("no acceptable authentication methods");
|
||||||
|
default:
|
||||||
|
SOCKS_FAIL ("protocol error");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
socks_5_send_req (int sockfd, struct socks_data *data, struct error **e)
|
||||||
|
{
|
||||||
|
uint8_t req[4 + 256 + 2], *p = req;
|
||||||
|
*p++ = 0x05; // version
|
||||||
|
*p++ = 0x01; // connect
|
||||||
|
*p++ = 0x00; // reserved
|
||||||
|
*p++ = data->address.type;
|
||||||
|
|
||||||
|
switch (data->address.type)
|
||||||
|
{
|
||||||
|
case SOCKS_IPV4:
|
||||||
|
memcpy (p, data->address.data.ipv4, sizeof data->address.data.ipv4);
|
||||||
|
p += sizeof data->address.data.ipv4;
|
||||||
|
break;
|
||||||
|
case SOCKS_DOMAIN:
|
||||||
|
{
|
||||||
|
size_t dlen = strlen (data->address.data.domain);
|
||||||
|
if (dlen > 255)
|
||||||
|
dlen = 255;
|
||||||
|
|
||||||
|
*p++ = dlen;
|
||||||
|
memcpy (p, data->address.data.domain, dlen);
|
||||||
|
p += dlen;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SOCKS_IPV6:
|
||||||
|
memcpy (p, data->address.data.ipv6, sizeof data->address.data.ipv6);
|
||||||
|
p += sizeof data->address.data.ipv6;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
*p++ = data->port >> 8;
|
||||||
|
*p++ = data->port;
|
||||||
|
|
||||||
|
if (send (sockfd, req, p - req, 0) == -1)
|
||||||
|
SOCKS_FAIL ("%s: %s", "send", strerror (errno));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
socks_5_process_resp (int sockfd, struct socks_data *data, struct error **e)
|
||||||
|
{
|
||||||
|
uint8_t resp_header[4];
|
||||||
|
ssize_t n;
|
||||||
|
SOCKS_RECV (resp_header, sizeof resp_header);
|
||||||
|
if (resp_header[0] != 0x05)
|
||||||
|
SOCKS_FAIL ("protocol error");
|
||||||
|
|
||||||
|
switch (resp_header[1])
|
||||||
|
{
|
||||||
|
case 0x00:
|
||||||
|
break;
|
||||||
|
case 0x01: SOCKS_FAIL ("general SOCKS server failure");
|
||||||
|
case 0x02: SOCKS_FAIL ("connection not allowed by ruleset");
|
||||||
|
case 0x03: SOCKS_FAIL ("network unreachable");
|
||||||
|
case 0x04: SOCKS_FAIL ("host unreachable");
|
||||||
|
case 0x05: SOCKS_FAIL ("connection refused");
|
||||||
|
case 0x06: SOCKS_FAIL ("TTL expired");
|
||||||
|
case 0x07: SOCKS_FAIL ("command not supported");
|
||||||
|
case 0x08: SOCKS_FAIL ("address type not supported");
|
||||||
|
default: SOCKS_FAIL ("protocol error");
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ((data->bound_address.type = resp_header[3]))
|
||||||
|
{
|
||||||
|
case SOCKS_IPV4:
|
||||||
|
SOCKS_RECV (data->bound_address.data.ipv4,
|
||||||
|
sizeof data->bound_address.data.ipv4);
|
||||||
|
break;
|
||||||
|
case SOCKS_IPV6:
|
||||||
|
SOCKS_RECV (data->bound_address.data.ipv6,
|
||||||
|
sizeof data->bound_address.data.ipv6);
|
||||||
|
break;
|
||||||
|
case SOCKS_DOMAIN:
|
||||||
|
{
|
||||||
|
uint8_t len;
|
||||||
|
SOCKS_RECV (&len, sizeof len);
|
||||||
|
|
||||||
|
char domain[len + 1];
|
||||||
|
SOCKS_RECV (domain, len);
|
||||||
|
domain[len] = '\0';
|
||||||
|
|
||||||
|
data->bound_address.data.domain = xstrdup (domain);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
SOCKS_FAIL ("protocol error");
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t port;
|
||||||
|
SOCKS_RECV (&port, sizeof port);
|
||||||
|
data->bound_port = ntohs (port);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef SOCKS_FAIL
|
||||||
|
#undef SOCKS_RECV
|
||||||
|
|
||||||
|
static bool
|
||||||
|
socks_5_connect (struct addrinfo *addresses, struct socks_data *data,
|
||||||
|
int *fd, struct error **e)
|
||||||
|
{
|
||||||
|
int sockfd;
|
||||||
|
if (!socks_get_socket (addresses, &sockfd, e))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!socks_5_auth (sockfd, data, e)
|
||||||
|
|| !socks_5_send_req (sockfd, data, e)
|
||||||
|
|| !socks_5_process_resp (sockfd, data, e))
|
||||||
|
{
|
||||||
|
xclose (sockfd);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*fd = sockfd;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
socks_connect (const char *socks_host, const char *socks_port,
|
||||||
|
const char *host, const char *port,
|
||||||
|
const char *username, const char *password, struct error **e)
|
||||||
|
{
|
||||||
|
int result = -1;
|
||||||
|
struct addrinfo gai_hints, *gai_result;
|
||||||
|
memset (&gai_hints, 0, sizeof gai_hints);
|
||||||
|
gai_hints.ai_socktype = SOCK_STREAM;
|
||||||
|
|
||||||
|
unsigned long port_no;
|
||||||
|
const struct servent *serv;
|
||||||
|
if ((serv = getservbyname (port, "tcp")))
|
||||||
|
port_no = (uint16_t) ntohs (serv->s_port);
|
||||||
|
else if (!xstrtoul (&port_no, port, 10) || !port_no || port_no > UINT16_MAX)
|
||||||
|
{
|
||||||
|
error_set (e, "invalid port number");
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
int err = getaddrinfo (socks_host, socks_port, &gai_hints, &gai_result);
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
error_set (e, "%s: %s", "getaddrinfo", gai_strerror (err));
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct socks_data data =
|
||||||
|
{ .username = username, .password = password, .port = port_no };
|
||||||
|
|
||||||
|
if (inet_pton (AF_INET, host, &data.address.data.ipv4) == 1)
|
||||||
|
data.address.type = SOCKS_IPV4;
|
||||||
|
else if (inet_pton (AF_INET6, host, &data.address.data.ipv6) == 1)
|
||||||
|
data.address.type = SOCKS_IPV6;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
data.address.type = SOCKS_DOMAIN;
|
||||||
|
data.address.data.domain = host;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!socks_5_connect (gai_result, &data, &result, NULL))
|
||||||
|
socks_4a_connect (gai_result, &data, &result, e);
|
||||||
|
|
||||||
|
if (data.bound_address.type == SOCKS_DOMAIN)
|
||||||
|
free ((char *) data.bound_address.data.domain);
|
||||||
|
freeaddrinfo (gai_result);
|
||||||
|
fail:
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
408
zyklonb.c
408
zyklonb.c
|
@ -19,9 +19,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include "common.c"
|
#include "common.c"
|
||||||
#include <arpa/inet.h>
|
|
||||||
|
|
||||||
// --- Configuration (application-specific) ------------------------------------
|
// --- Configuration (application-specific) ------------------------------------
|
||||||
|
|
||||||
|
@ -717,412 +715,6 @@ setup_recovery_handler (struct bot_context *ctx, struct error **e)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- SOCKS 5/4a (blocking implementation) ------------------------------------
|
|
||||||
|
|
||||||
// These are awkward protocols. Note that the `username' is used differently
|
|
||||||
// in SOCKS 4a and 5. In the former version, it is the username that you can
|
|
||||||
// get ident'ed against. In the latter version, it forms a pair with the
|
|
||||||
// password field and doesn't need to be an actual user on your machine.
|
|
||||||
|
|
||||||
// TODO: make a non-blocking poller-based version of this;
|
|
||||||
// either use c-ares or (even better) start another thread to do resolution
|
|
||||||
|
|
||||||
struct socks_addr
|
|
||||||
{
|
|
||||||
enum socks_addr_type
|
|
||||||
{
|
|
||||||
SOCKS_IPV4 = 1, ///< IPv4 address
|
|
||||||
SOCKS_DOMAIN = 3, ///< Domain name to be resolved
|
|
||||||
SOCKS_IPV6 = 4 ///< IPv6 address
|
|
||||||
}
|
|
||||||
type; ///< The type of this address
|
|
||||||
union
|
|
||||||
{
|
|
||||||
uint8_t ipv4[4]; ///< IPv4 address, network octet order
|
|
||||||
const char *domain; ///< Domain name
|
|
||||||
uint8_t ipv6[16]; ///< IPv6 address, network octet order
|
|
||||||
}
|
|
||||||
data; ///< The address itself
|
|
||||||
};
|
|
||||||
|
|
||||||
struct socks_data
|
|
||||||
{
|
|
||||||
struct socks_addr address; ///< Target address
|
|
||||||
uint16_t port; ///< Target port
|
|
||||||
const char *username; ///< Authentication username
|
|
||||||
const char *password; ///< Authentication password
|
|
||||||
|
|
||||||
struct socks_addr bound_address; ///< Bound address at the server
|
|
||||||
uint16_t bound_port; ///< Bound port at the server
|
|
||||||
};
|
|
||||||
|
|
||||||
static bool
|
|
||||||
socks_get_socket (struct addrinfo *addresses, int *fd, struct error **e)
|
|
||||||
{
|
|
||||||
int sockfd;
|
|
||||||
for (; addresses; addresses = addresses->ai_next)
|
|
||||||
{
|
|
||||||
sockfd = socket (addresses->ai_family,
|
|
||||||
addresses->ai_socktype, addresses->ai_protocol);
|
|
||||||
if (sockfd == -1)
|
|
||||||
continue;
|
|
||||||
set_cloexec (sockfd);
|
|
||||||
|
|
||||||
int yes = 1;
|
|
||||||
soft_assert (setsockopt (sockfd, SOL_SOCKET, SO_KEEPALIVE,
|
|
||||||
&yes, sizeof yes) != -1);
|
|
||||||
|
|
||||||
if (!connect (sockfd, addresses->ai_addr, addresses->ai_addrlen))
|
|
||||||
break;
|
|
||||||
xclose (sockfd);
|
|
||||||
}
|
|
||||||
if (!addresses)
|
|
||||||
{
|
|
||||||
error_set (e, "couldn't connect to the SOCKS server");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
*fd = sockfd;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define SOCKS_FAIL(...) \
|
|
||||||
BLOCK_START \
|
|
||||||
error_set (e, __VA_ARGS__); \
|
|
||||||
goto fail; \
|
|
||||||
BLOCK_END
|
|
||||||
#define SOCKS_RECV(buf, len) \
|
|
||||||
BLOCK_START \
|
|
||||||
if ((n = recv (sockfd, (buf), (len), 0)) == -1) \
|
|
||||||
SOCKS_FAIL ("%s: %s", "recv", strerror (errno)); \
|
|
||||||
if (n != (len)) \
|
|
||||||
SOCKS_FAIL ("%s: %s", "protocol error", "unexpected EOF"); \
|
|
||||||
BLOCK_END
|
|
||||||
|
|
||||||
static bool
|
|
||||||
socks_4a_connect (struct addrinfo *addresses, struct socks_data *data,
|
|
||||||
int *fd, struct error **e)
|
|
||||||
{
|
|
||||||
int sockfd;
|
|
||||||
if (!socks_get_socket (addresses, &sockfd, e))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const void *dest_ipv4 = "\x00\x00\x00\x01";
|
|
||||||
const char *dest_domain = NULL;
|
|
||||||
|
|
||||||
char buf[INET6_ADDRSTRLEN];
|
|
||||||
switch (data->address.type)
|
|
||||||
{
|
|
||||||
case SOCKS_IPV4:
|
|
||||||
dest_ipv4 = data->address.data.ipv4;
|
|
||||||
break;
|
|
||||||
case SOCKS_IPV6:
|
|
||||||
// About the best thing we can do, not sure if it works anywhere at all
|
|
||||||
if (!inet_ntop (AF_INET6, &data->address.data.ipv6, buf, sizeof buf))
|
|
||||||
SOCKS_FAIL ("%s: %s", "inet_ntop", strerror (errno));
|
|
||||||
dest_domain = buf;
|
|
||||||
break;
|
|
||||||
case SOCKS_DOMAIN:
|
|
||||||
dest_domain = data->address.data.domain;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct str req;
|
|
||||||
str_init (&req);
|
|
||||||
str_append_c (&req, 4); // version
|
|
||||||
str_append_c (&req, 1); // connect
|
|
||||||
|
|
||||||
str_append_c (&req, data->port >> 8); // higher bits of port
|
|
||||||
str_append_c (&req, data->port); // lower bits of port
|
|
||||||
str_append_data (&req, dest_ipv4, 4); // destination address
|
|
||||||
|
|
||||||
if (data->username)
|
|
||||||
str_append (&req, data->username);
|
|
||||||
str_append_c (&req, '\0');
|
|
||||||
|
|
||||||
if (dest_domain)
|
|
||||||
{
|
|
||||||
str_append (&req, dest_domain);
|
|
||||||
str_append_c (&req, '\0');
|
|
||||||
}
|
|
||||||
|
|
||||||
ssize_t n = send (sockfd, req.str, req.len, 0);
|
|
||||||
str_free (&req);
|
|
||||||
if (n == -1)
|
|
||||||
SOCKS_FAIL ("%s: %s", "send", strerror (errno));
|
|
||||||
|
|
||||||
uint8_t resp[8];
|
|
||||||
SOCKS_RECV (resp, sizeof resp);
|
|
||||||
if (resp[0] != 0)
|
|
||||||
SOCKS_FAIL ("protocol error");
|
|
||||||
|
|
||||||
switch (resp[1])
|
|
||||||
{
|
|
||||||
case 90:
|
|
||||||
break;
|
|
||||||
case 91:
|
|
||||||
SOCKS_FAIL ("request rejected or failed");
|
|
||||||
case 92:
|
|
||||||
SOCKS_FAIL ("%s: %s", "request rejected",
|
|
||||||
"SOCKS server cannot connect to identd on the client");
|
|
||||||
case 93:
|
|
||||||
SOCKS_FAIL ("%s: %s", "request rejected",
|
|
||||||
"identd reports different user-id");
|
|
||||||
default:
|
|
||||||
SOCKS_FAIL ("protocol error");
|
|
||||||
}
|
|
||||||
|
|
||||||
*fd = sockfd;
|
|
||||||
return true;
|
|
||||||
|
|
||||||
fail:
|
|
||||||
xclose (sockfd);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
#undef SOCKS_FAIL
|
|
||||||
#define SOCKS_FAIL(...) \
|
|
||||||
BLOCK_START \
|
|
||||||
error_set (e, __VA_ARGS__); \
|
|
||||||
return false; \
|
|
||||||
BLOCK_END
|
|
||||||
|
|
||||||
static bool
|
|
||||||
socks_5_userpass_auth (int sockfd, struct socks_data *data, struct error **e)
|
|
||||||
{
|
|
||||||
size_t ulen = strlen (data->username);
|
|
||||||
if (ulen > 255)
|
|
||||||
ulen = 255;
|
|
||||||
|
|
||||||
size_t plen = strlen (data->password);
|
|
||||||
if (plen > 255)
|
|
||||||
plen = 255;
|
|
||||||
|
|
||||||
uint8_t req[3 + ulen + plen], *p = req;
|
|
||||||
*p++ = 0x01; // version
|
|
||||||
*p++ = ulen; // username length
|
|
||||||
memcpy (p, data->username, ulen);
|
|
||||||
p += ulen;
|
|
||||||
*p++ = plen; // password length
|
|
||||||
memcpy (p, data->password, plen);
|
|
||||||
p += plen;
|
|
||||||
|
|
||||||
ssize_t n = send (sockfd, req, p - req, 0);
|
|
||||||
if (n == -1)
|
|
||||||
SOCKS_FAIL ("%s: %s", "send", strerror (errno));
|
|
||||||
|
|
||||||
uint8_t resp[2];
|
|
||||||
SOCKS_RECV (resp, sizeof resp);
|
|
||||||
if (resp[0] != 0x01)
|
|
||||||
SOCKS_FAIL ("protocol error");
|
|
||||||
if (resp[1] != 0x00)
|
|
||||||
SOCKS_FAIL ("authentication failure");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
socks_5_auth (int sockfd, struct socks_data *data, struct error **e)
|
|
||||||
{
|
|
||||||
bool can_auth = data->username && data->password;
|
|
||||||
|
|
||||||
uint8_t hello[4];
|
|
||||||
hello[0] = 0x05; // version
|
|
||||||
hello[1] = 1 + can_auth; // number of authentication methods
|
|
||||||
hello[2] = 0x00; // no authentication required
|
|
||||||
hello[3] = 0x02; // username/password
|
|
||||||
|
|
||||||
ssize_t n = send (sockfd, hello, 3 + can_auth, 0);
|
|
||||||
if (n == -1)
|
|
||||||
SOCKS_FAIL ("%s: %s", "send", strerror (errno));
|
|
||||||
|
|
||||||
uint8_t resp[2];
|
|
||||||
SOCKS_RECV (resp, sizeof resp);
|
|
||||||
if (resp[0] != 0x05)
|
|
||||||
SOCKS_FAIL ("protocol error");
|
|
||||||
|
|
||||||
switch (resp[1])
|
|
||||||
{
|
|
||||||
case 0x02:
|
|
||||||
if (!can_auth)
|
|
||||||
SOCKS_FAIL ("protocol error");
|
|
||||||
if (!socks_5_userpass_auth (sockfd, data, e))
|
|
||||||
return false;
|
|
||||||
case 0x00:
|
|
||||||
break;
|
|
||||||
case 0xFF:
|
|
||||||
SOCKS_FAIL ("no acceptable authentication methods");
|
|
||||||
default:
|
|
||||||
SOCKS_FAIL ("protocol error");
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
socks_5_send_req (int sockfd, struct socks_data *data, struct error **e)
|
|
||||||
{
|
|
||||||
uint8_t req[4 + 256 + 2], *p = req;
|
|
||||||
*p++ = 0x05; // version
|
|
||||||
*p++ = 0x01; // connect
|
|
||||||
*p++ = 0x00; // reserved
|
|
||||||
*p++ = data->address.type;
|
|
||||||
|
|
||||||
switch (data->address.type)
|
|
||||||
{
|
|
||||||
case SOCKS_IPV4:
|
|
||||||
memcpy (p, data->address.data.ipv4, sizeof data->address.data.ipv4);
|
|
||||||
p += sizeof data->address.data.ipv4;
|
|
||||||
break;
|
|
||||||
case SOCKS_DOMAIN:
|
|
||||||
{
|
|
||||||
size_t dlen = strlen (data->address.data.domain);
|
|
||||||
if (dlen > 255)
|
|
||||||
dlen = 255;
|
|
||||||
|
|
||||||
*p++ = dlen;
|
|
||||||
memcpy (p, data->address.data.domain, dlen);
|
|
||||||
p += dlen;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case SOCKS_IPV6:
|
|
||||||
memcpy (p, data->address.data.ipv6, sizeof data->address.data.ipv6);
|
|
||||||
p += sizeof data->address.data.ipv6;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
*p++ = data->port >> 8;
|
|
||||||
*p++ = data->port;
|
|
||||||
|
|
||||||
if (send (sockfd, req, p - req, 0) == -1)
|
|
||||||
SOCKS_FAIL ("%s: %s", "send", strerror (errno));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
socks_5_process_resp (int sockfd, struct socks_data *data, struct error **e)
|
|
||||||
{
|
|
||||||
uint8_t resp_header[4];
|
|
||||||
ssize_t n;
|
|
||||||
SOCKS_RECV (resp_header, sizeof resp_header);
|
|
||||||
if (resp_header[0] != 0x05)
|
|
||||||
SOCKS_FAIL ("protocol error");
|
|
||||||
|
|
||||||
switch (resp_header[1])
|
|
||||||
{
|
|
||||||
case 0x00:
|
|
||||||
break;
|
|
||||||
case 0x01: SOCKS_FAIL ("general SOCKS server failure");
|
|
||||||
case 0x02: SOCKS_FAIL ("connection not allowed by ruleset");
|
|
||||||
case 0x03: SOCKS_FAIL ("network unreachable");
|
|
||||||
case 0x04: SOCKS_FAIL ("host unreachable");
|
|
||||||
case 0x05: SOCKS_FAIL ("connection refused");
|
|
||||||
case 0x06: SOCKS_FAIL ("TTL expired");
|
|
||||||
case 0x07: SOCKS_FAIL ("command not supported");
|
|
||||||
case 0x08: SOCKS_FAIL ("address type not supported");
|
|
||||||
default: SOCKS_FAIL ("protocol error");
|
|
||||||
}
|
|
||||||
|
|
||||||
switch ((data->bound_address.type = resp_header[3]))
|
|
||||||
{
|
|
||||||
case SOCKS_IPV4:
|
|
||||||
SOCKS_RECV (data->bound_address.data.ipv4,
|
|
||||||
sizeof data->bound_address.data.ipv4);
|
|
||||||
break;
|
|
||||||
case SOCKS_IPV6:
|
|
||||||
SOCKS_RECV (data->bound_address.data.ipv6,
|
|
||||||
sizeof data->bound_address.data.ipv6);
|
|
||||||
break;
|
|
||||||
case SOCKS_DOMAIN:
|
|
||||||
{
|
|
||||||
uint8_t len;
|
|
||||||
SOCKS_RECV (&len, sizeof len);
|
|
||||||
|
|
||||||
char domain[len + 1];
|
|
||||||
SOCKS_RECV (domain, len);
|
|
||||||
domain[len] = '\0';
|
|
||||||
|
|
||||||
data->bound_address.data.domain = xstrdup (domain);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
SOCKS_FAIL ("protocol error");
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t port;
|
|
||||||
SOCKS_RECV (&port, sizeof port);
|
|
||||||
data->bound_port = ntohs (port);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
#undef SOCKS_FAIL
|
|
||||||
#undef SOCKS_RECV
|
|
||||||
|
|
||||||
static bool
|
|
||||||
socks_5_connect (struct addrinfo *addresses, struct socks_data *data,
|
|
||||||
int *fd, struct error **e)
|
|
||||||
{
|
|
||||||
int sockfd;
|
|
||||||
if (!socks_get_socket (addresses, &sockfd, e))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!socks_5_auth (sockfd, data, e)
|
|
||||||
|| !socks_5_send_req (sockfd, data, e)
|
|
||||||
|| !socks_5_process_resp (sockfd, data, e))
|
|
||||||
{
|
|
||||||
xclose (sockfd);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
*fd = sockfd;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
socks_connect (const char *socks_host, const char *socks_port,
|
|
||||||
const char *host, const char *port,
|
|
||||||
const char *username, const char *password, struct error **e)
|
|
||||||
{
|
|
||||||
int result = -1;
|
|
||||||
struct addrinfo gai_hints, *gai_result;
|
|
||||||
memset (&gai_hints, 0, sizeof gai_hints);
|
|
||||||
gai_hints.ai_socktype = SOCK_STREAM;
|
|
||||||
|
|
||||||
unsigned long port_no;
|
|
||||||
const struct servent *serv;
|
|
||||||
if ((serv = getservbyname (port, "tcp")))
|
|
||||||
port_no = (uint16_t) ntohs (serv->s_port);
|
|
||||||
else if (!xstrtoul (&port_no, port, 10) || !port_no || port_no > UINT16_MAX)
|
|
||||||
{
|
|
||||||
error_set (e, "invalid port number");
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
int err = getaddrinfo (socks_host, socks_port, &gai_hints, &gai_result);
|
|
||||||
if (err)
|
|
||||||
{
|
|
||||||
error_set (e, "%s: %s", "getaddrinfo", gai_strerror (err));
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct socks_data data =
|
|
||||||
{ .username = username, .password = password, .port = port_no };
|
|
||||||
|
|
||||||
if (inet_pton (AF_INET, host, &data.address.data.ipv4) == 1)
|
|
||||||
data.address.type = SOCKS_IPV4;
|
|
||||||
else if (inet_pton (AF_INET6, host, &data.address.data.ipv6) == 1)
|
|
||||||
data.address.type = SOCKS_IPV6;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
data.address.type = SOCKS_DOMAIN;
|
|
||||||
data.address.data.domain = host;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!socks_5_connect (gai_result, &data, &result, NULL))
|
|
||||||
socks_4a_connect (gai_result, &data, &result, e);
|
|
||||||
|
|
||||||
if (data.bound_address.type == SOCKS_DOMAIN)
|
|
||||||
free ((char *) data.bound_address.data.domain);
|
|
||||||
freeaddrinfo (gai_result);
|
|
||||||
fail:
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Plugins -----------------------------------------------------------------
|
// --- Plugins -----------------------------------------------------------------
|
||||||
|
|
||||||
/// The name of the special IRC command for interprocess communication
|
/// The name of the special IRC command for interprocess communication
|
||||||
|
|
Loading…
Reference in New Issue