summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordefanor <defanor@uberspace.net>2020-03-01 07:06:37 +0300
committerdefanor <defanor@uberspace.net>2020-03-01 08:25:17 +0300
commitbad8da311d20a1703adaba93b41f074423ba8ab8 (patch)
tree7e2173ba90e24416f79d56d12c4fe1bd94370ff3
parent38340aca46a68ab7971cc4dd14b20eac9d4195c3 (diff)
Add SOCKS5 support
-rw-r--r--README3
-rw-r--r--examples/basic.c2
-rw-r--r--src/Makefile.am4
-rw-r--r--src/rexmpp.c102
-rw-r--r--src/rexmpp.h12
-rw-r--r--src/rexmpp_socks.c121
-rw-r--r--src/rexmpp_socks.h63
-rw-r--r--src/rexmpp_tcp.c31
8 files changed, 298 insertions, 40 deletions
diff --git a/README b/README
index 825f64a..7e93a3f 100644
--- a/README
+++ b/README
@@ -40,8 +40,7 @@ A rough roadmap:
[+] "Happy Eyeballs" (RFC 8305).
[+] XEP-0368: SRV records for XMPP over TLS.
-[ ] SOCKS5 (RFC 1928) support. Perhaps a module with functions usable
- for SOCKS5 bytestreams at once.
+[+] SOCKS5 (RFC 1928) support. Implemented, though can be improved.
- Library refinement:
diff --git a/examples/basic.c b/examples/basic.c
index 681a0ac..46f6333 100644
--- a/examples/basic.c
+++ b/examples/basic.c
@@ -77,6 +77,8 @@ main () {
struct timeval *mtv;
int n = 0;
+ /* s.socks_host = "127.0.0.1"; */
+ /* s.socks_port = 4321; */
do {
diff --git a/src/Makefile.am b/src/Makefile.am
index 6883960..c3a62bf 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -8,7 +8,7 @@ AM_CFLAGS = -Werror -Wall -Wextra -Wno-pointer-sign -Wno-unused-parameter
lib_LTLIBRARIES = librexmpp.la
-librexmpp_la_SOURCES = rexmpp_tcp.h rexmpp_tcp.c rexmpp.h rexmpp.c
-include_HEADERS = rexmpp_tcp.h rexmpp.h
+librexmpp_la_SOURCES = rexmpp_tcp.h rexmpp_tcp.c rexmpp_socks.h rexmpp_socks.c rexmpp.h rexmpp.c
+include_HEADERS = rexmpp_tcp.h rexmpp_socks.h rexmpp.h
librexmpp_la_CFLAGS = $(AM_CFLAGS) $(LIBXML_CFLAGS) $(GNUTLS_CFLAGS) $(GSASL_CFLAGS) $(CARES_CFLAGS)
librexmpp_la_LIBADD = $(LIBXML_LIBS) $(GNUTLS_LIBS) $(GSASL_LIBS) $(CARES_LIBS)
diff --git a/src/rexmpp.c b/src/rexmpp.c
index 6061bc0..28597f3 100644
--- a/src/rexmpp.c
+++ b/src/rexmpp.c
@@ -21,6 +21,7 @@
#include "rexmpp.h"
#include "rexmpp_tcp.h"
+#include "rexmpp_socks.h"
void rexmpp_sax_start_elem_ns (rexmpp_t *s,
@@ -72,6 +73,8 @@ rexmpp_err_t rexmpp_init (rexmpp_t *s,
s->sasl_state = REXMPP_SASL_INACTIVE;
s->sm_state = REXMPP_SM_INACTIVE;
s->carbons_state = REXMPP_CARBONS_INACTIVE;
+ s->socks_host = NULL;
+ s->server_host = NULL;
s->send_buffer = NULL;
s->send_queue = NULL;
s->server_srv = NULL;
@@ -659,46 +662,60 @@ int rexmpp_stream_open (rexmpp_t *s) {
void rexmpp_process_conn_err (rexmpp_t *s, int err);
+void rexmpp_start_connecting (rexmpp_t *s) {
+ if (s->socks_host == NULL) {
+ rexmpp_log(s, LOG_DEBUG, "Connecting to %s:%u",
+ s->server_host, s->server_port);
+ rexmpp_process_conn_err(s,
+ rexmpp_tcp_conn_init(&s->server_connection,
+ s->server_host,
+ s->server_port));
+ } else {
+ rexmpp_log(s, LOG_DEBUG, "Connecting to %s:%u via %s:%u",
+ s->server_host, s->server_port,
+ s->socks_host, s->socks_port);
+ rexmpp_process_conn_err(s,
+ rexmpp_tcp_conn_init(&s->server_connection,
+ s->socks_host,
+ s->socks_port));
+ }
+}
+
void rexmpp_try_next_host (rexmpp_t *s) {
- const char *host;
- uint16_t port;
/* todo: check priorities and weights */
s->tls_state = REXMPP_TLS_INACTIVE;
if (s->server_srv_tls != NULL && s->server_srv_tls_cur == NULL) {
/* We have xmpps-client records available, but haven't tried any
of them yet. */
s->server_srv_tls_cur = s->server_srv_tls;
- host = s->server_srv_tls_cur->host;
- port = s->server_srv_tls_cur->port;
+ s->server_host = s->server_srv_tls_cur->host;
+ s->server_port = s->server_srv_tls_cur->port;
s->tls_state = REXMPP_TLS_AWAITING_DIRECT;
} else if (s->server_srv_tls_cur != NULL &&
s->server_srv_tls_cur->next != NULL) {
/* We have tried some xmpps-client records, but there is more. */
s->server_srv_tls_cur = s->server_srv_tls_cur->next;
- host = s->server_srv_tls_cur->host;
- port = s->server_srv_tls_cur->port;
+ s->server_host = s->server_srv_tls_cur->host;
+ s->server_port = s->server_srv_tls_cur->port;
s->tls_state = REXMPP_TLS_AWAITING_DIRECT;
} else if (s->server_srv != NULL && s->server_srv_cur == NULL) {
/* Starting with xmpp-client records. */
s->server_srv_cur = s->server_srv;
- host = s->server_srv_cur->host;
- port = s->server_srv_cur->port;
+ s->server_host = s->server_srv_cur->host;
+ s->server_port = s->server_srv_cur->port;
} else if (s->server_srv_tls_cur != NULL &&
s->server_srv_tls_cur->next != NULL) {
/* Advancing in xmpp-client records. */
s->server_srv_cur = s->server_srv_cur->next;
- host = s->server_srv_cur->host;
- port = s->server_srv_cur->port;
+ s->server_host = s->server_srv_cur->host;
+ s->server_port = s->server_srv_cur->port;
} else {
/* No candidate records left to try. Schedule a reconnect. */
rexmpp_cleanup(s);
rexmpp_schedule_reconnect(s);
return;
}
- rexmpp_log(s, LOG_DEBUG, "Connecting to %s:%d", host, port);
- rexmpp_process_conn_err(s,
- rexmpp_tcp_conn_init(&s->server_connection,
- host, port));
+ rexmpp_start_connecting(s);
}
void rexmpp_tls_handshake (rexmpp_t *s) {
@@ -781,22 +798,30 @@ void rexmpp_tls_start (rexmpp_t *s) {
rexmpp_tls_handshake(s);
}
+void rexmpp_connected_to_server (rexmpp_t *s) {
+ s->tcp_state = REXMPP_TCP_CONNECTED;
+ rexmpp_log(s, LOG_INFO, "Connected to the server");
+ s->reconnect_number = 0;
+ xmlCtxtResetPush(s->xml_parser, "", 0, "", "utf-8");
+ if (s->tls_state == REXMPP_TLS_AWAITING_DIRECT) {
+ rexmpp_tls_start(s);
+ } else {
+ rexmpp_stream_open(s);
+ }
+}
void rexmpp_process_conn_err (rexmpp_t *s, int err) {
s->tcp_state = REXMPP_TCP_CONNECTING;
if (err == REXMPP_CONN_DONE) {
- rexmpp_log(s, LOG_INFO, "Established a TCP connection");
- s->reconnect_number = 0;
- xmlCtxtResetPush(s->xml_parser, "", 0, "", "utf-8");
s->server_socket = rexmpp_tcp_conn_finish(&s->server_connection);
- s->tcp_state = REXMPP_TCP_CONNECTED;
- if (s->tls_state == REXMPP_TLS_AWAITING_DIRECT) {
- rexmpp_tls_start(s);
+ if (s->socks_host == NULL) {
+ rexmpp_connected_to_server(s);
} else {
- rexmpp_stream_open(s);
+ s->tcp_state = REXMPP_TCP_SOCKS;
+ rexmpp_socks_init(&s->server_socks_conn, s->server_socket,
+ s->server_host, s->server_port);
}
} else if (err != REXMPP_CONN_IN_PROGRESS) {
- rexmpp_log(s, LOG_WARNING, "Failed to connect");
if (err == REXMPP_CONN_ERROR) {
s->tcp_state = REXMPP_TCP_NONE;
} else {
@@ -821,11 +846,9 @@ void rexmpp_after_srv (rexmpp_t *s) {
if (s->server_srv == NULL && s->server_srv_tls == NULL) {
/* Failed to resolve anything: a fallback. */
- const char *host = jid_bare_to_host(s->initial_jid);
- uint16_t port = 5222;
- rexmpp_log(s, LOG_DEBUG, "Connecting to %s:%d", host, port);
- rexmpp_process_conn_err(s, rexmpp_tcp_conn_init(&s->server_connection,
- host, port));
+ s->server_host = jid_bare_to_host(s->initial_jid);
+ s->server_port = 5222;
+ rexmpp_start_connecting(s);
} else {
rexmpp_try_next_host(s);
}
@@ -1463,6 +1486,20 @@ rexmpp_err_t rexmpp_run (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) {
read_fds, write_fds));
}
+ /* SOCKS5 connection. */
+ if (s->tcp_state == REXMPP_TCP_SOCKS) {
+ enum socks_err err = rexmpp_socks_proceed(&s->server_socks_conn);
+ if (err == REXMPP_SOCKS_CONNECTED) {
+ rexmpp_connected_to_server(s);
+ } else if (err != REXMPP_SOCKS_E_AGAIN) {
+ rexmpp_log(s, LOG_ERR, "SOCKS5 connection failed.");
+ s->tcp_state = REXMPP_TCP_CONNECTION_FAILURE;
+ close(s->server_socket);
+ s->server_socket = -1;
+ rexmpp_try_next_host(s);
+ }
+ }
+
/* The things we do while connected. */
if (s->tcp_state == REXMPP_TCP_CONNECTED) {
@@ -1534,6 +1571,17 @@ int rexmpp_fds(rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) {
}
}
+ if (s->tcp_state == REXMPP_TCP_SOCKS) {
+ if (s->server_socks_conn.io_state == REXMPP_SOCKS_WRITING) {
+ FD_SET(s->server_socket, write_fds);
+ } else {
+ FD_SET(s->server_socket, read_fds);
+ }
+ if (s->server_socket + 1 > max_fd) {
+ max_fd = s->server_socket + 1;
+ }
+ }
+
if (s->tls_state == REXMPP_TLS_HANDSHAKE) {
if (gnutls_record_get_direction(s->gnutls_session) == 0) {
FD_SET(s->server_socket, read_fds);
diff --git a/src/rexmpp.h b/src/rexmpp.h
index 8c006e9..c28b490 100644
--- a/src/rexmpp.h
+++ b/src/rexmpp.h
@@ -14,6 +14,7 @@
#include <gsasl.h>
#include <libxml/tree.h>
#include "rexmpp_tcp.h"
+#include "rexmpp_socks.h"
typedef struct rexmpp rexmpp_t;
@@ -41,6 +42,7 @@ enum resolver_st {
enum tcp_st {
REXMPP_TCP_NONE,
REXMPP_TCP_CONNECTING,
+ REXMPP_TCP_SOCKS,
REXMPP_TCP_CONNECTED,
REXMPP_TCP_CLOSED,
REXMPP_TCP_CONNECTION_FAILURE,
@@ -158,6 +160,10 @@ struct rexmpp
/* Basic configuration. */
const char *initial_jid;
+ /* Socks settings. */
+ const char *socks_host;
+ uint16_t socks_port;
+
/* Resource limits. */
uint32_t stanza_queue_size;
uint32_t send_queue_size;
@@ -193,11 +199,17 @@ struct rexmpp
struct ares_srv_reply *server_srv_tls;
struct ares_srv_reply *server_srv_tls_cur;
+ /* The XMPP server we are connecting to. */
+ const char *server_host;
+ uint16_t server_port;
+
/* The primary socket used for communication with the server. */
int server_socket;
/* A structure used to establish a TCP connection. */
rexmpp_tcp_conn_t server_connection;
+ /* A structure used to establish a SOCKS5 connection. */
+ rexmpp_socks_t server_socks_conn;
/* Send buffer. NULL if there is nothing to send (and must not be
NULL if there is anything in the send queue). Not appending data
diff --git a/src/rexmpp_socks.c b/src/rexmpp_socks.c
new file mode 100644
index 0000000..48046bb
--- /dev/null
+++ b/src/rexmpp_socks.c
@@ -0,0 +1,121 @@
+#include <memory.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <errno.h>
+#include <stdint.h>
+#include <arpa/inet.h>
+
+#include "rexmpp_socks.h"
+
+enum socks_err
+rexmpp_socks_proceed (rexmpp_socks_t *s)
+{
+ ssize_t ret;
+ if (s->io_state == REXMPP_SOCKS_WRITING) {
+ ssize_t ret = send(s->fd, s->buf + s->buf_sent,
+ s->buf_len - s->buf_sent, 0);
+ if (ret > 0) {
+ s->buf_sent += ret;
+ if (s->buf_len == s->buf_sent) {
+ s->buf_len = 0;
+ s->io_state = REXMPP_SOCKS_READING;
+ }
+ } else if (errno == EAGAIN) {
+ return REXMPP_SOCKS_E_AGAIN;
+ } else {
+ return REXMPP_SOCKS_E_TCP;
+ }
+ } else if (s->io_state == REXMPP_SOCKS_READING) {
+ ret = recv(s->fd, s->buf + s->buf_len,
+ REXMPP_SOCKS_BUF_LEN - s->buf_len, 0);
+ if (ret > 0) {
+ s->buf_len += ret;
+ if (s->buf[0] != 5) {
+ return REXMPP_SOCKS_E_VERSION;
+ }
+ if (s->buf_len >= 2) {
+ s->socks_error = s->buf[1];
+ }
+ if (s->stage == REXMPP_SOCKS_AUTH) {
+ if (s->buf_len > 2) {
+ return REXMPP_SOCKS_E_REPLY;
+ }
+ if (s->buf_len == 2) {
+ if (s->socks_error != 0) {
+ return REXMPP_SOCKS_E_SOCKS;
+ }
+ /* It's okay to not authenticate, now we send a command. */
+ s->buf[0] = 5; /* SOCKS version 5 */
+ s->buf[1] = 1; /* Connect */
+ s->buf[2] = 0; /* Reserved */
+ s->buf[3] = 3; /* Domain name (todo: IP addresses) */
+ size_t len = strlen(s->host);
+ s->buf[4] = len;
+ memcpy(s->buf + 5, s->host, len);
+ uint16_t port = htons(s->port);
+ memcpy(s->buf + 5 + len, &port, 2);
+ s->buf_len = 7 + len;
+ s->buf_sent = 0;
+ s->stage = REXMPP_SOCKS_CMD;
+ s->io_state = REXMPP_SOCKS_WRITING;
+ return rexmpp_socks_proceed(s);
+ }
+ } else if (s->stage == REXMPP_SOCKS_CMD) {
+ if (s->buf_len >= 5) {
+ size_t full_len = 6;
+ if (s->buf[3] == 1) { /* IPv4 */
+ full_len += 4;
+ } else if (s->buf[3] == 3) { /* Domain name */
+ full_len += s->buf[4] + 1;
+ } else if (s->buf[3] == 4) { /* IPv6 */
+ full_len += 16;
+ } else {
+ return REXMPP_SOCKS_E_REPLY;
+ }
+ if (s->buf_len > full_len) {
+ return REXMPP_SOCKS_E_REPLY;
+ }
+ if (s->buf_len == full_len) {
+ if (s->socks_error != 0) {
+ return REXMPP_SOCKS_E_SOCKS;
+ }
+ /* We're done. */
+ s->stage = REXMPP_SOCKS_DONE;
+ return REXMPP_SOCKS_CONNECTED;
+ }
+ }
+ }
+ } else if (errno == EAGAIN) {
+ return REXMPP_SOCKS_E_AGAIN;
+ } else {
+ return REXMPP_SOCKS_E_TCP;
+ }
+ }
+ return REXMPP_SOCKS_E_AGAIN;
+}
+
+enum socks_err
+rexmpp_socks_init (rexmpp_socks_t *s,
+ int fd,
+ const char *host,
+ uint16_t port)
+{
+ s->fd = fd;
+ s->host = host;
+ s->port = port;
+ s->socks_error = 0;
+
+ if (strlen(host) > 255) {
+ return REXMPP_SOCKS_E_HOST;
+ }
+
+ /* Request authentication. */
+ s->stage = REXMPP_SOCKS_AUTH;
+ s->io_state = REXMPP_SOCKS_WRITING;
+ s->buf[0] = 5; /* SOCKS version 5 */
+ s->buf[1] = 1; /* 1 supported method */
+ s->buf[2] = 0; /* "no authentication required" */
+ s->buf_len = 3;
+ s->buf_sent = 0;
+ return rexmpp_socks_proceed(s);
+}
diff --git a/src/rexmpp_socks.h b/src/rexmpp_socks.h
new file mode 100644
index 0000000..213a92d
--- /dev/null
+++ b/src/rexmpp_socks.h
@@ -0,0 +1,63 @@
+/**
+ @file rexmpp_socks.h
+ @brief SOCKS5 connection establishment.
+ @author defanor <defanor@uberspace.net>
+ @date 2020
+ @copyright MIT license.
+*/
+
+#ifndef REXMPP_SOCKS_H
+#define REXMPP_SOCKS_H
+
+#include <unistd.h>
+
+
+#define REXMPP_SOCKS_BUF_LEN 300
+
+
+enum socks_io_state {
+ REXMPP_SOCKS_WRITING,
+ REXMPP_SOCKS_READING
+};
+
+enum socks_stage {
+ REXMPP_SOCKS_AUTH,
+ REXMPP_SOCKS_CMD,
+ REXMPP_SOCKS_DONE
+};
+
+enum socks_err {
+ REXMPP_SOCKS_CONNECTED,
+ REXMPP_SOCKS_E_AGAIN,
+ REXMPP_SOCKS_E_TCP,
+ REXMPP_SOCKS_E_REPLY,
+ REXMPP_SOCKS_E_VERSION,
+ REXMPP_SOCKS_E_SOCKS,
+ REXMPP_SOCKS_E_HOST
+};
+
+struct rexmpp_socks {
+ int fd;
+ const char *host;
+ uint16_t port;
+ enum socks_stage stage;
+ enum socks_io_state io_state;
+ int socks_error;
+ char buf[REXMPP_SOCKS_BUF_LEN];
+ size_t buf_len;
+ size_t buf_sent;
+};
+typedef struct rexmpp_socks rexmpp_socks_t;
+
+
+enum socks_err
+rexmpp_socks_proceed (rexmpp_socks_t *s);
+
+enum socks_err
+rexmpp_socks_init (rexmpp_socks_t *s,
+ int fd,
+ const char *host,
+ uint16_t port);
+
+
+#endif
diff --git a/src/rexmpp_tcp.c b/src/rexmpp_tcp.c
index 931ea43..91e4199 100644
--- a/src/rexmpp_tcp.c
+++ b/src/rexmpp_tcp.c
@@ -111,9 +111,6 @@ rexmpp_tcp_conn_init (rexmpp_tcp_conn_t *conn,
conn->fd = -1;
conn->next_connection_time.tv_sec = 0;
conn->next_connection_time.tv_usec = 0;
- if (conn->resolver_error) {
- return REXMPP_CONN_RESOLVER_ERROR;
- }
conn->resolution_v4 = REXMPP_CONN_RESOLUTION_INACTIVE;
conn->resolution_v6 = REXMPP_CONN_RESOLUTION_INACTIVE;
@@ -161,10 +158,12 @@ rexmpp_tcp_conn_init (rexmpp_tcp_conn_t *conn,
conn->connection_attempts++;
return REXMPP_CONN_IN_PROGRESS;
}
-
conn->resolution_v4 = REXMPP_CONN_RESOLUTION_WAITING;
conn->resolution_v6 = REXMPP_CONN_RESOLUTION_WAITING;
conn->resolver_error = ares_init(&(conn->resolver_channel));
+ if (conn->resolver_error != ARES_SUCCESS) {
+ return REXMPP_CONN_RESOLVER_ERROR;
+ }
ares_query(conn->resolver_channel, host,
ns_c_in, ns_t_aaaa, rexmpp_dns_aaaa_cb, conn);
@@ -223,6 +222,12 @@ rexmpp_tcp_conn_proceed (rexmpp_tcp_conn_t *conn,
ares_process(conn->resolver_channel, read_fds, write_fds);
}
+ if (conn->resolution_v4 == REXMPP_CONN_RESOLUTION_FAILURE &&
+ conn->resolution_v6 == REXMPP_CONN_RESOLUTION_FAILURE) {
+ /* Failed to resolve anything. */
+ return REXMPP_CONN_FAILURE;
+ }
+
/* New connections. */
int repeat;
do {
@@ -330,11 +335,14 @@ int rexmpp_tcp_conn_fds (rexmpp_tcp_conn_t *conn,
fd_set *write_fds)
{
int max_fd = 0, i;
- max_fd = ares_fds(conn->resolver_channel, read_fds, write_fds);
+ if (conn->resolution_v4 == REXMPP_CONN_RESOLUTION_WAITING ||
+ conn->resolution_v4 == REXMPP_CONN_RESOLUTION_WAITING) {
+ max_fd = ares_fds(conn->resolver_channel, read_fds, write_fds);
+ }
for (i = 0; i < REXMPP_TCP_MAX_CONNECTION_ATTEMPTS; i++) {
if (conn->sockets[i] != -1) {
FD_SET(conn->sockets[i], write_fds);
- if (max_fd < conn->sockets[i]) {
+ if (max_fd < conn->sockets[i] + 1) {
max_fd = conn->sockets[i] + 1;
}
}
@@ -347,10 +355,15 @@ struct timeval *rexmpp_tcp_conn_timeout (rexmpp_tcp_conn_t *conn,
struct timeval *tv)
{
struct timeval now;
- struct timeval *ret;
- ret = ares_timeout(conn->resolver_channel, max_tv, tv);
+ struct timeval *ret = max_tv;
+ if (conn->resolution_v4 == REXMPP_CONN_RESOLUTION_WAITING ||
+ conn->resolution_v4 == REXMPP_CONN_RESOLUTION_WAITING) {
+ ret = ares_timeout(conn->resolver_channel, max_tv, tv);
+ }
if (conn->resolution_v4 == REXMPP_CONN_RESOLUTION_SUCCESS ||
- conn->resolution_v6 == REXMPP_CONN_RESOLUTION_SUCCESS) {
+ conn->resolution_v6 == REXMPP_CONN_RESOLUTION_SUCCESS ||
+ (conn->resolution_v4 == REXMPP_CONN_RESOLUTION_INACTIVE &&
+ conn->resolution_v6 == REXMPP_CONN_RESOLUTION_INACTIVE)) {
gettimeofday(&now, NULL);
if (now.tv_sec < conn->next_connection_time.tv_sec ||
(now.tv_sec == conn->next_connection_time.tv_sec &&