From bad8da311d20a1703adaba93b41f074423ba8ab8 Mon Sep 17 00:00:00 2001 From: defanor Date: Sun, 1 Mar 2020 07:06:37 +0300 Subject: Add SOCKS5 support --- README | 3 +- examples/basic.c | 2 + src/Makefile.am | 4 +- src/rexmpp.c | 102 ++++++++++++++++++++++++++++++++------------ src/rexmpp.h | 12 ++++++ src/rexmpp_socks.c | 121 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/rexmpp_socks.h | 63 ++++++++++++++++++++++++++++ src/rexmpp_tcp.c | 31 ++++++++++---- 8 files changed, 298 insertions(+), 40 deletions(-) create mode 100644 src/rexmpp_socks.c create mode 100644 src/rexmpp_socks.h 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 #include #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 +#include +#include +#include +#include +#include + +#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 + @date 2020 + @copyright MIT license. +*/ + +#ifndef REXMPP_SOCKS_H +#define REXMPP_SOCKS_H + +#include + + +#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 && -- cgit v1.2.3