Rewrite socks to async
Not quite working yet, however it's a massive and important change.
This commit is contained in:
parent
b750590f18
commit
318b7400d1
879
common.c
879
common.c
|
@ -229,7 +229,7 @@ connector_step (struct connector *self)
|
||||||
self->on_connected (self->user_data, fd);
|
self->on_connected (self->user_data, fd);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (errno != EINPROGRESS)
|
if (errno != EINPROGRESS)
|
||||||
{
|
{
|
||||||
connector_notify_error (self, strerror (errno));
|
connector_notify_error (self, strerror (errno));
|
||||||
xclose (fd);
|
xclose (fd);
|
||||||
|
@ -324,15 +324,14 @@ connector_add_target (struct connector *self,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- SOCKS 5/4a (blocking implementation) ------------------------------------
|
// --- SOCKS 5/4a --------------------------------------------------------------
|
||||||
|
|
||||||
// These are awkward protocols. Note that the `username' is used differently
|
// Asynchronous SOCKS connector. Adds more stuff on top of the original.
|
||||||
// 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;
|
// Note that the `username' is used differently in SOCKS 4a and 5. In the
|
||||||
// either use c-ares or (even better) start another thread to do resolution
|
// 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.
|
||||||
|
|
||||||
struct socks_addr
|
struct socks_addr
|
||||||
{
|
{
|
||||||
|
@ -346,125 +345,183 @@ struct socks_addr
|
||||||
union
|
union
|
||||||
{
|
{
|
||||||
uint8_t ipv4[4]; ///< IPv4 address, network octet order
|
uint8_t ipv4[4]; ///< IPv4 address, network octet order
|
||||||
const char *domain; ///< Domain name
|
char *domain; ///< Domain name
|
||||||
uint8_t ipv6[16]; ///< IPv6 address, network octet order
|
uint8_t ipv6[16]; ///< IPv6 address, network octet order
|
||||||
}
|
}
|
||||||
data; ///< The address itself
|
data; ///< The address itself
|
||||||
};
|
};
|
||||||
|
|
||||||
struct socks_data
|
static void
|
||||||
|
socks_addr_free (struct socks_addr *self)
|
||||||
{
|
{
|
||||||
struct socks_addr address; ///< Target address
|
if (self->type == SOCKS_DOMAIN)
|
||||||
uint16_t port; ///< Target port
|
free (self->data.domain);
|
||||||
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
|
|
||||||
|
struct socks_target
|
||||||
|
{
|
||||||
|
LIST_HEADER (struct socks_target)
|
||||||
|
|
||||||
|
struct socks_addr address; ///< Target address
|
||||||
|
uint16_t port; ///< Target service port
|
||||||
};
|
};
|
||||||
|
|
||||||
static bool
|
enum socks_protocol
|
||||||
socks_get_socket (struct addrinfo *addresses, int *fd, struct error **e)
|
|
||||||
{
|
{
|
||||||
int sockfd;
|
SOCKS_5, ///< SOCKS5
|
||||||
for (; addresses; addresses = addresses->ai_next)
|
SOCKS_4A, ///< SOCKS4A
|
||||||
{
|
SOCKS_MAX ///< End of protocol
|
||||||
sockfd = socket (addresses->ai_family,
|
};
|
||||||
addresses->ai_socktype, addresses->ai_protocol);
|
|
||||||
if (sockfd == -1)
|
|
||||||
continue;
|
|
||||||
set_cloexec (sockfd);
|
|
||||||
|
|
||||||
int yes = 1;
|
struct socks_connector
|
||||||
soft_assert (setsockopt (sockfd, SOL_SOCKET, SO_KEEPALIVE,
|
{
|
||||||
&yes, sizeof yes) != -1);
|
struct connector *connector; ///< Proxy server iterator (effectively)
|
||||||
|
enum socks_protocol protocol_iter; ///< Protocol iterator
|
||||||
|
struct socks_target *targets_iter; ///< Targets iterator
|
||||||
|
|
||||||
if (!connect (sockfd, addresses->ai_addr, addresses->ai_addrlen))
|
// Negotiation:
|
||||||
break;
|
|
||||||
xclose (sockfd);
|
int socket_fd; ///< Current socket file descriptor
|
||||||
}
|
struct poller_fd socket_event; ///< Socket can be read from/written to
|
||||||
if (!addresses)
|
struct str read_buffer; ///< Read buffer
|
||||||
{
|
struct str write_buffer; ///< Write buffer
|
||||||
error_set (e, "couldn't connect to the SOCKS server");
|
|
||||||
return false;
|
struct poller_timer timeout; ///< Timeout timer
|
||||||
}
|
|
||||||
*fd = sockfd;
|
uint8_t bound_address_len; ///< Length of domain name
|
||||||
return true;
|
struct socks_addr bound_address; ///< Bound address at the server
|
||||||
}
|
uint16_t bound_port; ///< Bound port at the server
|
||||||
|
|
||||||
|
/// Process incoming data if there's enough of it available
|
||||||
|
bool (*on_data) (struct socks_connector *);
|
||||||
|
|
||||||
|
// Configuration:
|
||||||
|
|
||||||
|
const char *hostname; ///< SOCKS server hostname
|
||||||
|
const char *service; ///< SOCKS server service name or port
|
||||||
|
|
||||||
|
const char *username; ///< Username for authentication
|
||||||
|
const char *password; ///< Password for authentication
|
||||||
|
|
||||||
|
struct socks_target *targets; ///< Targets
|
||||||
|
struct socks_target *targets_tail; ///< Tail of targets
|
||||||
|
|
||||||
|
void *user_data; ///< User data for callbacks
|
||||||
|
|
||||||
|
// You may destroy the connector object in these two main callbacks:
|
||||||
|
|
||||||
|
/// Connection has been successfully established
|
||||||
|
void (*on_connected) (void *user_data, int socket);
|
||||||
|
/// Failed to establish a connection to either target
|
||||||
|
void (*on_failure) (void *user_data);
|
||||||
|
|
||||||
|
// Optional:
|
||||||
|
|
||||||
|
/// Connecting to a new address
|
||||||
|
void (*on_connecting) (void *user_data,
|
||||||
|
const char *address, const char *via, const char *version);
|
||||||
|
/// Connecting to the last address has failed
|
||||||
|
void (*on_error) (void *user_data, const char *error);
|
||||||
|
};
|
||||||
|
|
||||||
#define SOCKS_FAIL(...) \
|
#define SOCKS_FAIL(...) \
|
||||||
BLOCK_START \
|
BLOCK_START \
|
||||||
error_set (e, __VA_ARGS__); \
|
char *error = xstrdup_printf (__VA_ARGS__); \
|
||||||
goto fail; \
|
if (self->on_error) \
|
||||||
|
self->on_error (self->user_data, error); \
|
||||||
|
free (error); \
|
||||||
|
return false; \
|
||||||
BLOCK_END
|
BLOCK_END
|
||||||
#define SOCKS_RECV(buf, len) \
|
|
||||||
|
// FIXME: we need to cancel all events
|
||||||
|
#define SOCKS_DONE() \
|
||||||
BLOCK_START \
|
BLOCK_START \
|
||||||
if ((n = recv (sockfd, (buf), (len), 0)) == -1) \
|
int fd = self->socket_fd; \
|
||||||
SOCKS_FAIL ("%s: %s", "recv", strerror (errno)); \
|
set_blocking (fd, true); \
|
||||||
if (n != (len)) \
|
self->socket_fd = -1; \
|
||||||
SOCKS_FAIL ("%s: %s", "protocol error", "unexpected EOF"); \
|
self->on_connected (self->user_data, fd); \
|
||||||
|
return true; \
|
||||||
BLOCK_END
|
BLOCK_END
|
||||||
|
|
||||||
|
#define SOCKS_NEED_DATA(n) \
|
||||||
|
if (!socks_try_fill_read_buffer (self, (n))) \
|
||||||
|
return false; \
|
||||||
|
if (self->read_buffer.len < n) \
|
||||||
|
return true
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
socks_4a_connect (struct addrinfo *addresses, struct socks_data *data,
|
socks_try_fill_read_buffer (struct socks_connector *self, size_t n)
|
||||||
int *fd, struct error **e)
|
|
||||||
{
|
{
|
||||||
int sockfd;
|
ssize_t remains = (ssize_t) n - (ssize_t) self->read_buffer.len;
|
||||||
if (!socks_get_socket (addresses, &sockfd, e))
|
if (remains <= 0)
|
||||||
return false;
|
return true;
|
||||||
|
|
||||||
const void *dest_ipv4 = "\x00\x00\x00\x01";
|
ssize_t received;
|
||||||
const char *dest_domain = NULL;
|
str_ensure_space (&self->read_buffer, remains);
|
||||||
|
do
|
||||||
|
received = recv (self->socket_fd,
|
||||||
|
self->read_buffer.str + self->read_buffer.len, remains, 0);
|
||||||
|
while ((received == -1) && errno == EINTR);
|
||||||
|
|
||||||
char buf[INET6_ADDRSTRLEN];
|
if (received == 0)
|
||||||
switch (data->address.type)
|
SOCKS_FAIL ("%s: %s", "protocol error", "unexpected EOF");
|
||||||
|
if (received == -1 && errno != EAGAIN)
|
||||||
|
SOCKS_FAIL ("%s: %s", "recv", strerror (errno));
|
||||||
|
if (received > 0)
|
||||||
|
self->read_buffer.len += received;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
socks_try_flush_write_buffer (struct socks_connector *self)
|
||||||
|
{
|
||||||
|
struct str *wb = &self->write_buffer;
|
||||||
|
ssize_t n_written;
|
||||||
|
|
||||||
|
while (wb->len)
|
||||||
{
|
{
|
||||||
case SOCKS_IPV4:
|
n_written = send (self->socket_fd, wb->str, wb->len, 0);
|
||||||
dest_ipv4 = data->address.data.ipv4;
|
if (n_written >= 0)
|
||||||
break;
|
{
|
||||||
case SOCKS_IPV6:
|
str_remove_slice (wb, 0, n_written);
|
||||||
// About the best thing we can do, not sure if it works anywhere at all
|
continue;
|
||||||
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;
|
if (errno == EAGAIN)
|
||||||
str_init (&req);
|
break;
|
||||||
str_append_c (&req, 4); // version
|
if (errno == EINTR)
|
||||||
str_append_c (&req, 1); // connect
|
continue;
|
||||||
|
|
||||||
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));
|
SOCKS_FAIL ("%s: %s", "send", strerror (errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
uint8_t resp[8];
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
SOCKS_RECV (resp, sizeof resp);
|
|
||||||
if (resp[0] != 0)
|
static bool
|
||||||
|
socks_4a_finish (struct socks_connector *self)
|
||||||
|
{
|
||||||
|
SOCKS_NEED_DATA (8);
|
||||||
|
|
||||||
|
struct msg_unpacker unpacker;
|
||||||
|
msg_unpacker_init (&unpacker, self->read_buffer.str, self->read_buffer.len);
|
||||||
|
|
||||||
|
uint8_t null, status;
|
||||||
|
hard_assert (msg_unpacker_u8 (&unpacker, &null));
|
||||||
|
hard_assert (msg_unpacker_u8 (&unpacker, &status));
|
||||||
|
str_remove_slice (&self->read_buffer, 0, unpacker.offset);
|
||||||
|
|
||||||
|
if (null != 0)
|
||||||
SOCKS_FAIL ("protocol error");
|
SOCKS_FAIL ("protocol error");
|
||||||
|
|
||||||
switch (resp[1])
|
switch (status)
|
||||||
{
|
{
|
||||||
case 90:
|
case 90:
|
||||||
break;
|
SOCKS_DONE ();
|
||||||
case 91:
|
case 91:
|
||||||
SOCKS_FAIL ("request rejected or failed");
|
SOCKS_FAIL ("request rejected or failed");
|
||||||
case 92:
|
case 92:
|
||||||
|
@ -476,141 +533,138 @@ socks_4a_connect (struct addrinfo *addresses, struct socks_data *data,
|
||||||
default:
|
default:
|
||||||
SOCKS_FAIL ("protocol error");
|
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
|
static bool
|
||||||
socks_5_auth (int sockfd, struct socks_data *data, struct error **e)
|
socks_4a_start (struct socks_connector *self)
|
||||||
{
|
{
|
||||||
bool can_auth = data->username && data->password;
|
struct socks_target *target = self->targets_iter;
|
||||||
|
const void *dest_ipv4 = "\x00\x00\x00\x01";
|
||||||
|
const char *dest_domain = NULL;
|
||||||
|
|
||||||
uint8_t hello[4];
|
char buf[INET6_ADDRSTRLEN];
|
||||||
hello[0] = 0x05; // version
|
switch (target->address.type)
|
||||||
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:
|
case SOCKS_IPV4:
|
||||||
memcpy (p, data->address.data.ipv4, sizeof data->address.data.ipv4);
|
dest_ipv4 = target->address.data.ipv4;
|
||||||
p += sizeof 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, &target->address.data.ipv6, buf, sizeof buf))
|
||||||
|
SOCKS_FAIL ("%s: %s", "inet_ntop", strerror (errno));
|
||||||
|
dest_domain = buf;
|
||||||
break;
|
break;
|
||||||
case SOCKS_DOMAIN:
|
case SOCKS_DOMAIN:
|
||||||
|
dest_domain = target->address.data.domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct str *wb = &self->write_buffer;
|
||||||
|
str_init (wb);
|
||||||
|
str_append_c (wb, 4); // version
|
||||||
|
str_append_c (wb, 1); // connect
|
||||||
|
|
||||||
|
str_append_c (wb, target->port >> 8); // higher bits of port
|
||||||
|
str_append_c (wb, target->port); // lower bits of port
|
||||||
|
str_append_data (wb, dest_ipv4, 4); // destination address
|
||||||
|
|
||||||
|
if (self->username)
|
||||||
|
str_append (wb, self->username);
|
||||||
|
str_append_c (wb, '\0');
|
||||||
|
|
||||||
|
if (dest_domain)
|
||||||
{
|
{
|
||||||
size_t dlen = strlen (data->address.data.domain);
|
str_append (wb, dest_domain);
|
||||||
if (dlen > 255)
|
str_append_c (wb, '\0');
|
||||||
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)
|
self->on_data = socks_4a_finish;
|
||||||
SOCKS_FAIL ("%s: %s", "send", strerror (errno));
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
static bool
|
||||||
|
socks_5_request_port (struct socks_connector *self)
|
||||||
|
{
|
||||||
|
SOCKS_NEED_DATA (2);
|
||||||
|
|
||||||
|
struct msg_unpacker unpacker;
|
||||||
|
msg_unpacker_init (&unpacker, self->read_buffer.str, self->read_buffer.len);
|
||||||
|
hard_assert (msg_unpacker_u16 (&unpacker, &self->bound_port));
|
||||||
|
str_remove_slice (&self->read_buffer, 0, unpacker.offset);
|
||||||
|
|
||||||
|
SOCKS_DONE ();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
socks_5_request_ipv4 (struct socks_connector *self)
|
||||||
|
{
|
||||||
|
size_t len = sizeof self->bound_address.data.ipv4;
|
||||||
|
SOCKS_NEED_DATA (len);
|
||||||
|
memcpy (self->bound_address.data.ipv4, self->read_buffer.str, len);
|
||||||
|
str_remove_slice (&self->read_buffer, 0, len);
|
||||||
|
|
||||||
|
self->on_data = socks_5_request_port;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
socks_5_process_resp (int sockfd, struct socks_data *data, struct error **e)
|
socks_5_request_ipv6 (struct socks_connector *self)
|
||||||
{
|
{
|
||||||
uint8_t resp_header[4];
|
size_t len = sizeof self->bound_address.data.ipv6;
|
||||||
ssize_t n;
|
SOCKS_NEED_DATA (len);
|
||||||
SOCKS_RECV (resp_header, sizeof resp_header);
|
memcpy (self->bound_address.data.ipv6, self->read_buffer.str, len);
|
||||||
if (resp_header[0] != 0x05)
|
str_remove_slice (&self->read_buffer, 0, len);
|
||||||
|
|
||||||
|
self->on_data = socks_5_request_port;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
socks_5_request_domain_data (struct socks_connector *self)
|
||||||
|
{
|
||||||
|
size_t len = self->bound_address_len;
|
||||||
|
SOCKS_NEED_DATA (len);
|
||||||
|
self->bound_address.data.domain = xstrndup (self->read_buffer.str, len);
|
||||||
|
str_remove_slice (&self->read_buffer, 0, len);
|
||||||
|
|
||||||
|
self->on_data = socks_5_request_port;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
socks_5_request_domain (struct socks_connector *self)
|
||||||
|
{
|
||||||
|
SOCKS_NEED_DATA (1);
|
||||||
|
|
||||||
|
struct msg_unpacker unpacker;
|
||||||
|
msg_unpacker_init (&unpacker, self->read_buffer.str, self->read_buffer.len);
|
||||||
|
hard_assert (msg_unpacker_u8 (&unpacker, &self->bound_address_len));
|
||||||
|
str_remove_slice (&self->read_buffer, 0, unpacker.offset);
|
||||||
|
|
||||||
|
self->on_data = socks_5_request_domain_data;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
socks_5_request_finish (struct socks_connector *self)
|
||||||
|
{
|
||||||
|
SOCKS_NEED_DATA (4);
|
||||||
|
|
||||||
|
struct msg_unpacker unpacker;
|
||||||
|
msg_unpacker_init (&unpacker, self->read_buffer.str, self->read_buffer.len);
|
||||||
|
|
||||||
|
uint8_t version, status, reserved, type;
|
||||||
|
hard_assert (msg_unpacker_u8 (&unpacker, &version));
|
||||||
|
hard_assert (msg_unpacker_u8 (&unpacker, &status));
|
||||||
|
hard_assert (msg_unpacker_u8 (&unpacker, &reserved));
|
||||||
|
hard_assert (msg_unpacker_u8 (&unpacker, &type));
|
||||||
|
str_remove_slice (&self->read_buffer, 0, unpacker.offset);
|
||||||
|
|
||||||
|
if (version != 0x05)
|
||||||
SOCKS_FAIL ("protocol error");
|
SOCKS_FAIL ("protocol error");
|
||||||
|
|
||||||
switch (resp_header[1])
|
switch (status)
|
||||||
{
|
{
|
||||||
case 0x00:
|
case 0x00:
|
||||||
break;
|
break;
|
||||||
|
@ -625,109 +679,368 @@ socks_5_process_resp (int sockfd, struct socks_data *data, struct error **e)
|
||||||
default: SOCKS_FAIL ("protocol error");
|
default: SOCKS_FAIL ("protocol error");
|
||||||
}
|
}
|
||||||
|
|
||||||
switch ((data->bound_address.type = resp_header[3]))
|
switch ((self->bound_address.type = type))
|
||||||
|
{
|
||||||
|
case SOCKS_IPV4: self->on_data = socks_5_request_ipv4; return true;
|
||||||
|
case SOCKS_IPV6: self->on_data = socks_5_request_ipv6; return true;
|
||||||
|
case SOCKS_DOMAIN: self->on_data = socks_5_request_domain; return true;
|
||||||
|
default: SOCKS_FAIL ("protocol error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
socks_5_request_start (struct socks_connector *self)
|
||||||
|
{
|
||||||
|
struct socks_target *target = self->targets_iter;
|
||||||
|
struct str *wb = &self->write_buffer;
|
||||||
|
str_append_c (wb, 0x05); // version
|
||||||
|
str_append_c (wb, 0x01); // connect
|
||||||
|
str_append_c (wb, 0x00); // reserved
|
||||||
|
str_append_c (wb, target->address.type);
|
||||||
|
|
||||||
|
switch (target->address.type)
|
||||||
{
|
{
|
||||||
case SOCKS_IPV4:
|
case SOCKS_IPV4:
|
||||||
SOCKS_RECV (data->bound_address.data.ipv4,
|
str_append_data (wb,
|
||||||
sizeof data->bound_address.data.ipv4);
|
target->address.data.ipv4, sizeof target->address.data.ipv4);
|
||||||
break;
|
|
||||||
case SOCKS_IPV6:
|
|
||||||
SOCKS_RECV (data->bound_address.data.ipv6,
|
|
||||||
sizeof data->bound_address.data.ipv6);
|
|
||||||
break;
|
break;
|
||||||
case SOCKS_DOMAIN:
|
case SOCKS_DOMAIN:
|
||||||
{
|
{
|
||||||
uint8_t len;
|
size_t dlen = strlen (target->address.data.domain);
|
||||||
SOCKS_RECV (&len, sizeof len);
|
if (dlen > 255)
|
||||||
|
dlen = 255;
|
||||||
|
|
||||||
char domain[len + 1];
|
str_append_c (wb, dlen);
|
||||||
SOCKS_RECV (domain, len);
|
str_append_data (wb, target->address.data.domain, dlen);
|
||||||
domain[len] = '\0';
|
|
||||||
|
|
||||||
data->bound_address.data.domain = xstrdup (domain);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case SOCKS_IPV6:
|
||||||
|
str_append_data (wb,
|
||||||
|
target->address.data.ipv6, sizeof target->address.data.ipv6);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
str_append_c (wb, target->port >> 8);
|
||||||
|
str_append_c (wb, target->port);
|
||||||
|
|
||||||
|
self->on_data = socks_5_request_finish;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
socks_5_userpass_finish (struct socks_connector *self)
|
||||||
|
{
|
||||||
|
SOCKS_NEED_DATA (2);
|
||||||
|
|
||||||
|
struct msg_unpacker unpacker;
|
||||||
|
msg_unpacker_init (&unpacker, self->read_buffer.str, self->read_buffer.len);
|
||||||
|
|
||||||
|
uint8_t version, status;
|
||||||
|
hard_assert (msg_unpacker_u8 (&unpacker, &version));
|
||||||
|
hard_assert (msg_unpacker_u8 (&unpacker, &status));
|
||||||
|
str_remove_slice (&self->read_buffer, 0, unpacker.offset);
|
||||||
|
|
||||||
|
if (version != 0x01)
|
||||||
|
SOCKS_FAIL ("protocol error");
|
||||||
|
if (status != 0x00)
|
||||||
|
SOCKS_FAIL ("authentication failure");
|
||||||
|
return socks_5_request_start (self);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
socks_5_userpass_start (struct socks_connector *self)
|
||||||
|
{
|
||||||
|
size_t ulen = strlen (self->username);
|
||||||
|
if (ulen > 255)
|
||||||
|
ulen = 255;
|
||||||
|
|
||||||
|
size_t plen = strlen (self->password);
|
||||||
|
if (plen > 255)
|
||||||
|
plen = 255;
|
||||||
|
|
||||||
|
struct str *wb = &self->write_buffer;
|
||||||
|
str_append_c (wb, 0x01); // version
|
||||||
|
str_append_c (wb, ulen); // username length
|
||||||
|
str_append_data (wb, self->username, ulen);
|
||||||
|
str_append_c (wb, plen); // password length
|
||||||
|
str_append_data (wb, self->password, plen);
|
||||||
|
|
||||||
|
self->on_data = socks_5_userpass_finish;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
socks_5_auth_finish (struct socks_connector *self)
|
||||||
|
{
|
||||||
|
SOCKS_NEED_DATA (2);
|
||||||
|
|
||||||
|
struct msg_unpacker unpacker;
|
||||||
|
msg_unpacker_init (&unpacker, self->read_buffer.str, self->read_buffer.len);
|
||||||
|
|
||||||
|
uint8_t version, method;
|
||||||
|
hard_assert (msg_unpacker_u8 (&unpacker, &version));
|
||||||
|
hard_assert (msg_unpacker_u8 (&unpacker, &method));
|
||||||
|
str_remove_slice (&self->read_buffer, 0, unpacker.offset);
|
||||||
|
|
||||||
|
if (version != 0x05)
|
||||||
|
SOCKS_FAIL ("protocol error");
|
||||||
|
|
||||||
|
bool can_auth = self->username && self->password;
|
||||||
|
|
||||||
|
switch (method)
|
||||||
|
{
|
||||||
|
case 0x02:
|
||||||
|
if (!can_auth)
|
||||||
|
SOCKS_FAIL ("protocol error");
|
||||||
|
|
||||||
|
return socks_5_userpass_start (self);
|
||||||
|
case 0x00:
|
||||||
|
return socks_5_request_start (self);
|
||||||
|
case 0xFF:
|
||||||
|
SOCKS_FAIL ("no acceptable authentication methods");
|
||||||
default:
|
default:
|
||||||
SOCKS_FAIL ("protocol error");
|
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
|
static bool
|
||||||
socks_5_connect (struct addrinfo *addresses, struct socks_data *data,
|
socks_5_auth_start (struct socks_connector *self)
|
||||||
int *fd, struct error **e)
|
|
||||||
{
|
{
|
||||||
int sockfd;
|
bool can_auth = self->username && self->password;
|
||||||
if (!socks_get_socket (addresses, &sockfd, e))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!socks_5_auth (sockfd, data, e)
|
struct str *wb = &self->write_buffer;
|
||||||
|| !socks_5_send_req (sockfd, data, e)
|
str_append_c (wb, 0x05); // version
|
||||||
|| !socks_5_process_resp (sockfd, data, e))
|
str_append_c (wb, 1 + can_auth); // number of authentication methods
|
||||||
{
|
str_append_c (wb, 0x00); // no authentication required
|
||||||
xclose (sockfd);
|
str_append_c (wb, 0x02); // username/password
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
*fd = sockfd;
|
self->on_data = socks_5_auth_finish;
|
||||||
return true;
|
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;
|
static void socks_connector_step (struct socks_connector *self);
|
||||||
|
|
||||||
|
static void
|
||||||
|
socks_connector_on_connected (void *user_data, int socket_fd)
|
||||||
|
{
|
||||||
|
set_blocking (socket_fd, false);
|
||||||
|
|
||||||
|
struct socks_connector *self = user_data;
|
||||||
|
self->socket_fd = socket_fd;
|
||||||
|
self->socket_event.fd = socket_fd;
|
||||||
|
poller_fd_set (&self->socket_event, POLLIN | POLLOUT);
|
||||||
|
str_reset (&self->read_buffer);
|
||||||
|
str_reset (&self->write_buffer);
|
||||||
|
|
||||||
|
if ((self->protocol_iter == SOCKS_5 && socks_5_auth_start (self))
|
||||||
|
|| (self->protocol_iter == SOCKS_4A && socks_4a_start (self)))
|
||||||
|
return;
|
||||||
|
|
||||||
|
self->on_failure (self->user_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
socks_connector_on_failure (void *user_data)
|
||||||
|
{
|
||||||
|
struct socks_connector *self = user_data;
|
||||||
|
// TODO: skip SOCKS server on connection failure
|
||||||
|
socks_connector_step (self);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
socks_connector_on_connecting (void *user_data, const char *via)
|
||||||
|
{
|
||||||
|
struct socks_connector *self = user_data;
|
||||||
|
if (!self->on_connecting)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// TODO: reconstruct the address from the current target iterator,
|
||||||
|
// or just store it in unprocessed form
|
||||||
|
char *address = format_host_port_pair ("", "");
|
||||||
|
self->on_connecting (self->user_data, address, via,
|
||||||
|
self->protocol_iter ? "SOCKS4A" : "SOCKS5");
|
||||||
|
free (address);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
socks_connector_on_error (void *user_data, const char *error)
|
||||||
|
{
|
||||||
|
struct socks_connector *self = user_data;
|
||||||
|
// TODO: skip protocol on protocol failure
|
||||||
|
self->on_error (self->user_data, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
socks_connector_start (struct socks_connector *self)
|
||||||
|
{
|
||||||
|
struct connector *connector =
|
||||||
|
self->connector = xcalloc (1, sizeof *connector);
|
||||||
|
connector_init (connector, self->socket_event.poller);
|
||||||
|
|
||||||
|
connector->user_data = self;
|
||||||
|
connector->on_connected = socks_connector_on_connected;
|
||||||
|
connector->on_connecting = socks_connector_on_connecting;
|
||||||
|
connector->on_error = socks_connector_on_error;
|
||||||
|
connector->on_failure = socks_connector_on_failure;
|
||||||
|
|
||||||
|
// TODO: let's rather call on_error and on_failure instead on error
|
||||||
|
hard_assert (connector_add_target (connector,
|
||||||
|
self->hostname, self->service, NULL));
|
||||||
|
|
||||||
|
connector_step (connector);
|
||||||
|
poller_timer_set (&self->timeout, 60 * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
socks_connector_step (struct socks_connector *self)
|
||||||
|
{
|
||||||
|
// Destroy current connector if needed
|
||||||
|
if (self->connector)
|
||||||
|
{
|
||||||
|
connector_free (self->connector);
|
||||||
|
free (self->connector);
|
||||||
|
self->connector = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// At the lowest level we iterate over all addresses for the SOCKS server;
|
||||||
|
// this is done automatically by the connector
|
||||||
|
|
||||||
|
// Then we iterate over available protocols
|
||||||
|
if (++self->protocol_iter != SOCKS_MAX)
|
||||||
|
{
|
||||||
|
socks_connector_start (self);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// At the highest level we iterate over possible targets
|
||||||
|
self->protocol_iter = 0;
|
||||||
|
if (self->targets_iter && (self->targets_iter = self->targets_iter->next))
|
||||||
|
{
|
||||||
|
socks_connector_start (self);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: we need to cancel all events
|
||||||
|
self->on_failure (self->user_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
socks_connector_on_ready
|
||||||
|
(const struct pollfd *pfd, struct socks_connector *self)
|
||||||
|
{
|
||||||
|
(void) pfd;
|
||||||
|
|
||||||
|
if (!self->on_data (self) || !socks_try_flush_write_buffer (self))
|
||||||
|
{
|
||||||
|
// We've failed this target, let's try to move on
|
||||||
|
// FIXME: we need to cancel all events
|
||||||
|
socks_connector_step (self);
|
||||||
|
}
|
||||||
|
// If we successfully establish the connection, then the FD is reset to -1
|
||||||
|
else if (self->socket_fd != -1)
|
||||||
|
{
|
||||||
|
poller_fd_set (&self->socket_event,
|
||||||
|
self->write_buffer.len ? (POLLIN | POLLOUT) : POLLIN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
socks_connector_on_timeout (struct socks_connector *self)
|
||||||
|
{
|
||||||
|
if (self->on_error)
|
||||||
|
self->on_error (self->user_data, "timeout");
|
||||||
|
|
||||||
|
// FIXME: we need to cancel all events
|
||||||
|
self->on_failure (self->user_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
static void
|
||||||
|
socks_connector_init (struct socks_connector *self, struct poller *poller)
|
||||||
|
{
|
||||||
|
memset (self, 0, sizeof *self);
|
||||||
|
|
||||||
|
poller_fd_init (&self->socket_event, poller, (self->socket_fd = -1));
|
||||||
|
self->socket_event.dispatcher = (poller_fd_fn) socks_connector_on_ready;
|
||||||
|
self->socket_event.user_data = self;
|
||||||
|
|
||||||
|
poller_timer_init (&self->timeout, poller);
|
||||||
|
self->timeout.dispatcher = (poller_timer_fn) socks_connector_on_timeout;
|
||||||
|
self->timeout.user_data = self;
|
||||||
|
|
||||||
|
str_init (&self->read_buffer);
|
||||||
|
str_init (&self->write_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
socks_connector_free (struct socks_connector *self)
|
||||||
|
{
|
||||||
|
if (self->connector)
|
||||||
|
{
|
||||||
|
connector_free (self->connector);
|
||||||
|
free (self->connector);
|
||||||
|
}
|
||||||
|
|
||||||
|
poller_fd_reset (&self->socket_event);
|
||||||
|
poller_timer_reset (&self->timeout);
|
||||||
|
|
||||||
|
if (self->socket_fd != -1)
|
||||||
|
xclose (self->socket_fd);
|
||||||
|
|
||||||
|
str_free (&self->read_buffer);
|
||||||
|
str_free (&self->write_buffer);
|
||||||
|
|
||||||
|
LIST_FOR_EACH (struct socks_target, iter, self->targets)
|
||||||
|
{
|
||||||
|
socks_addr_free (&iter->address);
|
||||||
|
free (iter);
|
||||||
|
}
|
||||||
|
|
||||||
|
socks_addr_free (&self->bound_address);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
socks_connector_add_target (struct socks_connector *self,
|
||||||
|
const char *host, const char *service, struct error **e)
|
||||||
|
{
|
||||||
|
unsigned long port;
|
||||||
const struct servent *serv;
|
const struct servent *serv;
|
||||||
if ((serv = getservbyname (port, "tcp")))
|
if ((serv = getservbyname (service, "tcp")))
|
||||||
port_no = (uint16_t) ntohs (serv->s_port);
|
port = (uint16_t) ntohs (serv->s_port);
|
||||||
else if (!xstrtoul (&port_no, port, 10) || !port_no || port_no > UINT16_MAX)
|
else if (!xstrtoul (&port, service, 10) || !port || port > UINT16_MAX)
|
||||||
{
|
{
|
||||||
error_set (e, "invalid port number");
|
error_set (e, "invalid port number");
|
||||||
goto fail;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int err = getaddrinfo (socks_host, socks_port, &gai_hints, &gai_result);
|
struct socks_target *target = xcalloc (1, sizeof *target);
|
||||||
if (err)
|
if (inet_pton (AF_INET, host, &target->address.data.ipv4) == 1)
|
||||||
{
|
target->address.type = SOCKS_IPV4;
|
||||||
error_set (e, "%s: %s", "getaddrinfo", gai_strerror (err));
|
else if (inet_pton (AF_INET6, host, &target->address.data.ipv6) == 1)
|
||||||
goto fail;
|
target->address.type = SOCKS_IPV6;
|
||||||
}
|
|
||||||
|
|
||||||
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
|
else
|
||||||
{
|
{
|
||||||
data.address.type = SOCKS_DOMAIN;
|
target->address.type = SOCKS_DOMAIN;
|
||||||
data.address.data.domain = host;
|
target->address.data.domain = xstrdup (host);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!socks_5_connect (gai_result, &data, &result, NULL))
|
target->port = port;
|
||||||
socks_4a_connect (gai_result, &data, &result, e);
|
LIST_APPEND_WITH_TAIL (self->targets, self->targets_tail, target);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (data.bound_address.type == SOCKS_DOMAIN)
|
static void
|
||||||
free ((char *) data.bound_address.data.domain);
|
socks_connector_run (struct socks_connector *self)
|
||||||
freeaddrinfo (gai_result);
|
{
|
||||||
fail:
|
// XXX: do we need some better error checking in here?
|
||||||
return result;
|
hard_assert (self->hostname);
|
||||||
|
hard_assert (self->targets);
|
||||||
|
|
||||||
|
self->targets_iter = self->targets;
|
||||||
|
self->protocol_iter = 0;
|
||||||
|
socks_connector_start (self);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- CTCP decoding -----------------------------------------------------------
|
// --- CTCP decoding -----------------------------------------------------------
|
||||||
|
|
138
degesch.c
138
degesch.c
|
@ -1116,6 +1116,7 @@ struct server
|
||||||
|
|
||||||
enum server_state state; ///< Connection state
|
enum server_state state; ///< Connection state
|
||||||
struct connector *connector; ///< Connection establisher
|
struct connector *connector; ///< Connection establisher
|
||||||
|
struct socks_connector *socks_conn; ///< SOCKS connection establisher
|
||||||
unsigned reconnect_attempt; ///< Number of reconnect attempt
|
unsigned reconnect_attempt; ///< Number of reconnect attempt
|
||||||
bool manual_disconnect; ///< Don't reconnect after disconnect
|
bool manual_disconnect; ///< Don't reconnect after disconnect
|
||||||
|
|
||||||
|
@ -1265,6 +1266,11 @@ server_free (struct server *self)
|
||||||
connector_free (self->connector);
|
connector_free (self->connector);
|
||||||
free (self->connector);
|
free (self->connector);
|
||||||
}
|
}
|
||||||
|
if (self->socks_conn)
|
||||||
|
{
|
||||||
|
socks_connector_free (self->socks_conn);
|
||||||
|
free (self->socks_conn);
|
||||||
|
}
|
||||||
|
|
||||||
if (self->transport
|
if (self->transport
|
||||||
&& self->transport->cleanup)
|
&& self->transport->cleanup)
|
||||||
|
@ -3591,10 +3597,16 @@ irc_shutdown (struct server *s)
|
||||||
static void
|
static void
|
||||||
irc_destroy_connector (struct server *s)
|
irc_destroy_connector (struct server *s)
|
||||||
{
|
{
|
||||||
connector_free (s->connector);
|
if (s->connector)
|
||||||
|
connector_free (s->connector);
|
||||||
free (s->connector);
|
free (s->connector);
|
||||||
s->connector = NULL;
|
s->connector = NULL;
|
||||||
|
|
||||||
|
if (s->socks_conn)
|
||||||
|
socks_connector_free (s->socks_conn);
|
||||||
|
free (s->socks_conn);
|
||||||
|
s->socks_conn = NULL;
|
||||||
|
|
||||||
// Not connecting anymore
|
// Not connecting anymore
|
||||||
s->state = IRC_DISCONNECTED;
|
s->state = IRC_DISCONNECTED;
|
||||||
}
|
}
|
||||||
|
@ -4352,6 +4364,28 @@ irc_finish_connection (struct server *s, int socket)
|
||||||
refresh_prompt (s->ctx);
|
refresh_prompt (s->ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
irc_split_host_port (char *s, char **host, char **port)
|
||||||
|
{
|
||||||
|
char *colon = strrchr (s, ':');
|
||||||
|
if (colon)
|
||||||
|
{
|
||||||
|
*colon = '\0';
|
||||||
|
*port = ++colon;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
*port = "6667";
|
||||||
|
|
||||||
|
// Unwrap IPv6 addresses in format_host_port_pair() format
|
||||||
|
size_t host_end = strlen (s) - 1;
|
||||||
|
if (*s == '[' && s[host_end] == ']')
|
||||||
|
s++[host_end] = '\0';
|
||||||
|
|
||||||
|
*host = s;
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
static void
|
static void
|
||||||
irc_on_connector_connecting (void *user_data, const char *address)
|
irc_on_connector_connecting (void *user_data, const char *address)
|
||||||
{
|
{
|
||||||
|
@ -4382,32 +4416,13 @@ irc_on_connector_connected (void *user_data, int socket)
|
||||||
irc_finish_connection (s, socket);
|
irc_finish_connection (s, socket);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
|
||||||
irc_split_host_port (char *s, char **host, char **port)
|
|
||||||
{
|
|
||||||
char *colon = strrchr (s, ':');
|
|
||||||
if (colon)
|
|
||||||
{
|
|
||||||
*colon = '\0';
|
|
||||||
*port = ++colon;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
*port = "6667";
|
|
||||||
|
|
||||||
// Unwrap IPv6 addresses in format_host_port_pair() format
|
|
||||||
size_t host_end = strlen (s) - 1;
|
|
||||||
if (*s == '[' && s[host_end] == ']')
|
|
||||||
s++[host_end] = '\0';
|
|
||||||
|
|
||||||
*host = s;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
irc_setup_connector (struct server *s,
|
irc_setup_connector (struct server *s,
|
||||||
const struct str_vector *addresses, struct error **e)
|
const struct str_vector *addresses, struct error **e)
|
||||||
{
|
{
|
||||||
struct connector *connector = xmalloc (sizeof *connector);
|
struct connector *connector = xmalloc (sizeof *connector);
|
||||||
connector_init (connector, &s->ctx->poller);
|
connector_init (connector, &s->ctx->poller);
|
||||||
|
s->connector = connector;
|
||||||
|
|
||||||
connector->user_data = s;
|
connector->user_data = s;
|
||||||
connector->on_connecting = irc_on_connector_connecting;
|
connector->on_connecting = irc_on_connector_connecting;
|
||||||
|
@ -4415,69 +4430,76 @@ irc_setup_connector (struct server *s,
|
||||||
connector->on_connected = irc_on_connector_connected;
|
connector->on_connected = irc_on_connector_connected;
|
||||||
connector->on_failure = irc_on_connector_failure;
|
connector->on_failure = irc_on_connector_failure;
|
||||||
|
|
||||||
s->state = IRC_CONNECTING;
|
|
||||||
s->connector = connector;
|
|
||||||
|
|
||||||
for (size_t i = 0; i < addresses->len; i++)
|
for (size_t i = 0; i < addresses->len; i++)
|
||||||
{
|
{
|
||||||
char *host, *port;
|
char *host, *port;
|
||||||
irc_split_host_port (addresses->vector[i], &host, &port);
|
irc_split_host_port (addresses->vector[i], &host, &port);
|
||||||
|
|
||||||
if (!connector_add_target (connector, host, port, e))
|
if (!connector_add_target (connector, host, port, e))
|
||||||
{
|
|
||||||
irc_destroy_connector (s);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
connector_step (connector);
|
connector_step (connector);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
// TODO: see if we can further merge code for the two connectors, for example
|
||||||
|
// by making SOCKS 4A and 5 mere plugins for the connector, or by using
|
||||||
|
// a virtual interface common to them both (seems more likely)
|
||||||
|
|
||||||
|
static void
|
||||||
|
irc_on_socks_connecting (void *user_data,
|
||||||
|
const char *address, const char *via, const char *version)
|
||||||
|
{
|
||||||
|
struct server *s = user_data;
|
||||||
|
log_server_status (s, s->buffer,
|
||||||
|
"Connecting to #s via #s (#s)...", address, via, version);
|
||||||
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
irc_initiate_connect_socks (struct server *s,
|
irc_setup_connector_socks (struct server *s,
|
||||||
const struct str_vector *addresses, struct error **e)
|
const struct str_vector *addresses, struct error **e)
|
||||||
{
|
{
|
||||||
const char *socks_host = get_config_string (s->config, "socks_host");
|
const char *socks_host = get_config_string (s->config, "socks_host");
|
||||||
int64_t socks_port_int = get_config_integer (s->config, "socks_port");
|
int64_t socks_port_int = get_config_integer (s->config, "socks_port");
|
||||||
|
|
||||||
const char *socks_username =
|
|
||||||
get_config_string (s->config, "socks_username");
|
|
||||||
const char *socks_password =
|
|
||||||
get_config_string (s->config, "socks_password");
|
|
||||||
|
|
||||||
if (!socks_host)
|
if (!socks_host)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// FIXME: we only try the first address (still better than nothing)
|
struct socks_connector *connector = xmalloc (sizeof *connector);
|
||||||
char *irc_host, *irc_port;
|
socks_connector_init (connector, &s->ctx->poller);
|
||||||
irc_split_host_port (addresses->vector[0], &irc_host, &irc_port);
|
s->socks_conn = connector;
|
||||||
|
|
||||||
char *socks_port = xstrdup_printf ("%" PRIi64, socks_port_int);
|
// FIXME: the SOCKS connector may outlive these values
|
||||||
|
connector->hostname = socks_host;
|
||||||
|
// FIXME: memory leak
|
||||||
|
connector->service = xstrdup_printf ("%" PRIi64, socks_port_int);
|
||||||
|
connector->username = get_config_string (s->config, "socks_username");
|
||||||
|
connector->password = get_config_string (s->config, "socks_password");
|
||||||
|
|
||||||
log_server_status (s, s->buffer, "Connecting to #&s via #&s...",
|
connector->user_data = s;
|
||||||
format_host_port_pair (irc_host, irc_port),
|
connector->on_connecting = irc_on_socks_connecting;
|
||||||
format_host_port_pair (socks_host, socks_port));
|
connector->on_error = irc_on_connector_error;
|
||||||
|
connector->on_connected = irc_on_connector_connected;
|
||||||
|
connector->on_failure = irc_on_connector_failure;
|
||||||
|
|
||||||
// TODO: the SOCKS code needs a rewrite so that we don't block on it either;
|
for (size_t i = 0; i < addresses->len; i++)
|
||||||
// perhaps it could act as a special kind of connector
|
|
||||||
struct error *error = NULL;
|
|
||||||
bool result = true;
|
|
||||||
int fd = socks_connect (socks_host, socks_port, irc_host, irc_port,
|
|
||||||
socks_username, socks_password, &error);
|
|
||||||
if (fd != -1)
|
|
||||||
irc_finish_connection (s, fd);
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
error_set (e, "%s: %s", "SOCKS connection failed", error->message);
|
char *host, *port;
|
||||||
error_free (error);
|
irc_split_host_port (addresses->vector[i], &host, &port);
|
||||||
result = false;
|
|
||||||
|
if (!socks_connector_add_target (connector, host, port, e))
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
free (socks_port);
|
socks_connector_run (connector);
|
||||||
return result;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
static void
|
static void
|
||||||
irc_initiate_connect (struct server *s)
|
irc_initiate_connect (struct server *s)
|
||||||
{
|
{
|
||||||
|
@ -4497,13 +4519,17 @@ irc_initiate_connect (struct server *s)
|
||||||
cstr_split_ignore_empty (addresses, ',', &servers);
|
cstr_split_ignore_empty (addresses, ',', &servers);
|
||||||
|
|
||||||
struct error *e = NULL;
|
struct error *e = NULL;
|
||||||
if (!irc_initiate_connect_socks (s, &servers, &e) && !e)
|
if (!irc_setup_connector_socks (s, &servers, &e) && !e)
|
||||||
irc_setup_connector (s, &servers, &e);
|
irc_setup_connector (s, &servers, &e);
|
||||||
|
|
||||||
str_vector_free (&servers);
|
str_vector_free (&servers);
|
||||||
|
|
||||||
if (e)
|
if (!e)
|
||||||
|
s->state = IRC_CONNECTING;
|
||||||
|
else
|
||||||
{
|
{
|
||||||
|
irc_destroy_connector (s);
|
||||||
|
|
||||||
log_server_error (s, s->buffer, "#s", e->message);
|
log_server_error (s, s->buffer, "#s", e->message);
|
||||||
error_free (e);
|
error_free (e);
|
||||||
irc_queue_reconnect (s);
|
irc_queue_reconnect (s);
|
||||||
|
|
114
zyklonb.c
114
zyklonb.c
|
@ -1647,16 +1647,99 @@ end:
|
||||||
irc_reset_connection_timeouts (ctx);
|
irc_reset_connection_timeouts (ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
// The bot is currently mostly synchronous (which also makes it shorter),
|
||||||
|
// however our current SOCKS code is not, hence we must wrap it.
|
||||||
|
|
||||||
|
struct irc_socks_data
|
||||||
|
{
|
||||||
|
struct bot_context *ctx; ///< Bot context
|
||||||
|
struct poller inner_poller; ///< Special inner poller
|
||||||
|
bool polling; ///< Inner poller is no longer needed
|
||||||
|
struct socks_connector connector; ///< SOCKS connector
|
||||||
|
bool succeeded; ///< Were we successful in connecting?
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
irc_on_socks_connected (void *user_data, int socket)
|
||||||
|
{
|
||||||
|
struct irc_socks_data *data = user_data;
|
||||||
|
data->ctx->irc_fd = socket;
|
||||||
|
data->succeeded = true;
|
||||||
|
data->polling = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
irc_on_socks_failure (void *user_data)
|
||||||
|
{
|
||||||
|
struct irc_socks_data *data = user_data;
|
||||||
|
data->succeeded = false;
|
||||||
|
data->polling = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
irc_on_socks_connecting (void *user_data,
|
||||||
|
const char *address, const char *via, const char *version)
|
||||||
|
{
|
||||||
|
(void) user_data;
|
||||||
|
print_status ("connecting to %s via %s (%s)...", address, via, version);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
irc_on_socks_error (void *user_data, const char *error)
|
||||||
|
{
|
||||||
|
(void) user_data;
|
||||||
|
print_error ("%s: %s", "SOCKS connection failed", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
irc_establish_connection_socks (struct bot_context *ctx,
|
||||||
|
const char *socks_host, const char *socks_port,
|
||||||
|
const char *host, const char *service, struct error **e)
|
||||||
|
{
|
||||||
|
struct irc_socks_data data;
|
||||||
|
struct poller *poller = &data.inner_poller;
|
||||||
|
struct socks_connector *connector = &data.connector;
|
||||||
|
|
||||||
|
data.ctx = ctx;
|
||||||
|
poller_init (poller);
|
||||||
|
data.polling = true;
|
||||||
|
socks_connector_init (connector, poller);
|
||||||
|
data.succeeded = false;
|
||||||
|
|
||||||
|
connector->hostname = socks_host;
|
||||||
|
connector->service = socks_port;
|
||||||
|
connector->username = str_map_find (&ctx->config, "socks_username");
|
||||||
|
connector->password = str_map_find (&ctx->config, "socks_password");
|
||||||
|
|
||||||
|
connector->on_connected = irc_on_socks_connected;
|
||||||
|
connector->on_connecting = irc_on_socks_connecting;
|
||||||
|
connector->on_error = irc_on_socks_error;
|
||||||
|
connector->on_failure = irc_on_socks_failure;
|
||||||
|
connector->user_data = &data;
|
||||||
|
|
||||||
|
if (socks_connector_add_target (connector, host, service, e))
|
||||||
|
{
|
||||||
|
socks_connector_run (connector);
|
||||||
|
while (data.polling)
|
||||||
|
poller_run (poller);
|
||||||
|
}
|
||||||
|
|
||||||
|
socks_connector_free (connector);
|
||||||
|
poller_free (poller);
|
||||||
|
return data.succeeded;
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
irc_connect (struct bot_context *ctx, struct error **e)
|
irc_connect (struct bot_context *ctx, struct error **e)
|
||||||
{
|
{
|
||||||
const char *irc_host = str_map_find (&ctx->config, "irc_host");
|
const char *irc_host = str_map_find (&ctx->config, "irc_host");
|
||||||
const char *irc_port = str_map_find (&ctx->config, "irc_port");
|
const char *irc_port = str_map_find (&ctx->config, "irc_port");
|
||||||
|
|
||||||
const char *socks_host = str_map_find (&ctx->config, "socks_host");
|
const char *socks_host = str_map_find (&ctx->config, "socks_host");
|
||||||
const char *socks_port = str_map_find (&ctx->config, "socks_port");
|
const char *socks_port = str_map_find (&ctx->config, "socks_port");
|
||||||
const char *socks_username = str_map_find (&ctx->config, "socks_username");
|
|
||||||
const char *socks_password = str_map_find (&ctx->config, "socks_password");
|
|
||||||
|
|
||||||
const char *nickname = str_map_find (&ctx->config, "nickname");
|
const char *nickname = str_map_find (&ctx->config, "nickname");
|
||||||
const char *username = str_map_find (&ctx->config, "username");
|
const char *username = str_map_find (&ctx->config, "username");
|
||||||
|
@ -1678,26 +1761,11 @@ irc_connect (struct bot_context *ctx, struct error **e)
|
||||||
if (!irc_get_boolean_from_config (ctx, "ssl", &use_ssl, e))
|
if (!irc_get_boolean_from_config (ctx, "ssl", &use_ssl, e))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (socks_host)
|
bool connected = socks_host
|
||||||
{
|
? irc_establish_connection_socks (ctx,
|
||||||
char *address = format_host_port_pair (irc_host, irc_port);
|
socks_host, socks_port, irc_host, irc_port, e)
|
||||||
char *socks_address = format_host_port_pair (socks_host, socks_port);
|
: irc_establish_connection (ctx, irc_host, irc_port, e);
|
||||||
print_status ("connecting to %s via %s...", address, socks_address);
|
if (!connected)
|
||||||
free (socks_address);
|
|
||||||
free (address);
|
|
||||||
|
|
||||||
struct error *error = NULL;
|
|
||||||
int fd = socks_connect (socks_host, socks_port, irc_host, irc_port,
|
|
||||||
socks_username, socks_password, &error);
|
|
||||||
if (fd == -1)
|
|
||||||
{
|
|
||||||
error_set (e, "%s: %s", "SOCKS connection failed", error->message);
|
|
||||||
error_free (error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
ctx->irc_fd = fd;
|
|
||||||
}
|
|
||||||
else if (!irc_establish_connection (ctx, irc_host, irc_port, e))
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (use_ssl && !irc_initialize_ssl (ctx, e))
|
if (use_ssl && !irc_initialize_ssl (ctx, e))
|
||||||
|
|
Loading…
Reference in New Issue