summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordefanor <defanor@uberspace.net>2023-05-04 21:29:28 +0300
committerdefanor <defanor@uberspace.net>2023-05-05 06:49:45 +0300
commitf0f947ac56f58ac5e047bdcafcdc8b9a1a1e34ee (patch)
treee742265eda45730f8656fd810d20adfb7581cd5c
parent938963c1d1c6a9b929d28a90030332d4f397ca61 (diff)
Introduce an alternative implementation of rexmpp_socks in Rust
Possibly other modules will follow. Aiming to provide memory-safe(r) alternatives, while keeping the C versions as well.
-rw-r--r--README3
-rw-r--r--configure.ac9
-rw-r--r--src/Makefile.am19
-rw-r--r--src/rexmpp_socks.rs180
4 files changed, 208 insertions, 3 deletions
diff --git a/README b/README
index 6e6e27a..9fdc4f6 100644
--- a/README
+++ b/README
@@ -16,7 +16,7 @@ it easy to implement a decent client application using it.
Current dependencies: libxml2, libgcrypt. Optionally gsasl, libunbound
or c-ares, gnutls with gnutls-dane or openssl, icu-i18n, gpgme, curl,
-libnice (with glib), libsrtp2.
+libnice (with glib), libsrtp2, rustc.
A rough roadmap:
@@ -56,6 +56,7 @@ A rough roadmap:
optional usage of alternative ones. Though left libxml2 for now:
could reuse existing libxml2 bindings that way.
[.] Automated testing.
+[.] Alternative module implementations in Rust.
- IM features:
diff --git a/configure.ac b/configure.ac
index 26d0a76..e9588fa 100644
--- a/configure.ac
+++ b/configure.ac
@@ -13,6 +13,15 @@ AC_CONFIG_FILES([Makefile src/Makefile tests/Makefile rexmpp.pc Doxyfile])
AC_PROG_CC
AM_PROG_AR
+AC_ARG_WITH([rust],
+ AS_HELP_STRING([--with-rust],
+ [use available Rust implementations of modules]))
+
+AS_IF([test "x$with_rust" == "xyes"],
+ [AC_PATH_PROG([RUSTC], [rustc], [notfound])
+ AS_IF([test "x$RUSTC" == "xnotfound"], [AC_MSG_ERROR([rustc is required])])])
+AM_CONDITIONAL([USE_RUST], [test "x$with_rust" == "xyes"])
+
LT_INIT
# Checks for libraries and related parameters.
diff --git a/src/Makefile.am b/src/Makefile.am
index 9fff4c8..c0c7073 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -7,12 +7,10 @@ AM_CFLAGS = -Werror -Wall -Wextra -pedantic -std=gnu99 \
# etc), it shouldn't matter. Later it would be nice to abstract XML
# manipulations anyway, to allow libexpat as an alternative.
-
lib_LTLIBRARIES = librexmpp.la
librexmpp_la_SOURCES = rexmpp_roster.h rexmpp_roster.c \
rexmpp_tcp.h rexmpp_tcp.c \
- rexmpp_socks.h rexmpp_socks.c \
rexmpp.h rexmpp.c \
rexmpp_dns.h rexmpp_dns.c \
rexmpp_tls.h rexmpp_tls.c \
@@ -24,6 +22,7 @@ librexmpp_la_SOURCES = rexmpp_roster.h rexmpp_roster.c \
rexmpp_jingle.h rexmpp_jingle.c \
rexmpp_base64.h rexmpp_base64.c \
rexmpp_sasl.h rexmpp_sasl.c
+
include_HEADERS = config.h rexmpp_roster.h rexmpp_tcp.h rexmpp_socks.h rexmpp.h \
rexmpp_dns.h rexmpp_tls.h rexmpp_jid.h rexmpp_openpgp.h rexmpp_console.h \
rexmpp_pubsub.h rexmpp_http_upload.h rexmpp_jingle.h rexmpp_base64.h \
@@ -37,3 +36,19 @@ librexmpp_la_LIBADD = $(LIBXML_LIBS) \
$(GNUTLS_LIBS) $(LIBDANE_LIBS) $(OPENSSL_LIBS) \
$(GSASL_LIBS) $(UNBOUND_LIBS) $(CARES_LIBS) $(GPGME_LIBS) $(ICU_I18N_LIBS) \
$(LIBGCRYPT_LIBS) $(CURL_LIBS) $(NICE_LIBS) $(GLIB_LIBS) $(SRTP_LIBS)
+librexmpp_la_LDFLAGS = []
+
+if USE_RUST
+librexmpp_rust_a_SOURCES = rexmpp_socks.rs
+noinst_LIBRARIES = librexmpp_rust.a
+librexmpp_la_LIBADD += librexmpp_rust.a
+librexmpp_la_LDFLAGS += -L. -lpthread -ldl
+
+# todo: setup it properly, to rebuild whenever the sources are
+# changed.
+librexmpp_rust.a:
+ rustc --crate-type=staticlib -o $@ $(librexmpp_rust_a_SOURCES)
+
+else
+librexmpp_la_SOURCES += rexmpp_socks.h rexmpp_socks.c
+endif
diff --git a/src/rexmpp_socks.rs b/src/rexmpp_socks.rs
new file mode 100644
index 0000000..1828de1
--- /dev/null
+++ b/src/rexmpp_socks.rs
@@ -0,0 +1,180 @@
+// For rustc and libstd-rust version 1.48
+
+use std::os::raw::{c_int, c_char};
+use std::ffi::CStr;
+use std::net::TcpStream;
+use std::os::unix::io::{FromRawFd, IntoRawFd};
+use std::io::Write;
+use std::io::Read;
+use std::io::ErrorKind;
+use std::convert::TryFrom;
+
+const REXMPP_SOCKS_BUF_LEN: usize = 300;
+
+#[derive(PartialEq)]
+#[repr(C)]
+enum SocksIOState {
+ Writing,
+ Reading
+}
+
+#[derive(PartialEq)]
+#[repr(C)]
+enum SocksStage {
+ Auth,
+ Cmd,
+ Done
+}
+
+#[derive(PartialEq)]
+#[repr(C)]
+enum SocksErr {
+ Connected,
+ EAgain,
+ ETCP,
+ EReply,
+ EVersion,
+ ESocks,
+ EHost
+}
+
+#[repr(C)]
+pub struct RexmppSocks {
+ fd: c_int,
+ host: *const c_char,
+ port: u16,
+ stage: SocksStage,
+ io_state: SocksIOState,
+ socks_error: c_int,
+ buf: [u8; REXMPP_SOCKS_BUF_LEN],
+ buf_len: usize,
+ buf_sent: usize
+}
+
+#[no_mangle]
+extern "C" fn rexmpp_socks_proceed (s : &mut RexmppSocks) -> SocksErr {
+ if s.io_state == SocksIOState::Writing {
+ let mut stream : TcpStream = unsafe { TcpStream::from_raw_fd(s.fd) };
+ let ret = stream.write(&s.buf[s.buf_sent .. s.buf_len]);
+ // Make sure the connection is not closed by TcpStream.
+ TcpStream::into_raw_fd(stream);
+ match ret {
+ Ok(sent) => {
+ s.buf_sent += sent;
+ if s.buf_len == s.buf_sent {
+ s.buf_len = 0;
+ s.io_state = SocksIOState::Reading;
+ }
+ }
+ Err(error) => match error.kind() {
+ ErrorKind::WouldBlock => return SocksErr::EAgain,
+ _ => return SocksErr::ETCP
+ }
+ }
+ } else if s.io_state == SocksIOState::Reading {
+ let mut stream : TcpStream = unsafe { TcpStream::from_raw_fd(s.fd) };
+ let ret = stream.read(&mut s.buf[s.buf_len ..]);
+ // Make sure the connection is not closed by TcpStream.
+ TcpStream::into_raw_fd(stream);
+ match ret {
+ Ok(received) => {
+ s.buf_len += received;
+ if s.buf[0] != 5 {
+ return SocksErr::EVersion;
+ }
+ if s.buf_len >= 2 {
+ s.socks_error = s.buf[1].into();
+ }
+ if s.stage == SocksStage::Auth {
+ if s.buf_len > 2 {
+ return SocksErr::EReply;
+ }
+ if s.buf_len == 2 {
+ if s.socks_error != 0 {
+ return SocksErr::ESocks;
+ }
+ 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)
+ let host_cstr : &CStr =
+ unsafe { CStr::from_ptr(s.host) };
+ let host_len = host_cstr.to_bytes().len();
+ match u8::try_from(host_len) {
+ Ok(u) => { s.buf[4] = u }
+ Err(_) => return SocksErr::EHost
+ }
+ s.buf[5 .. 5 + host_len].
+ copy_from_slice(&host_cstr.to_bytes());
+ s.buf[5 + host_len .. 7 + host_len].
+ copy_from_slice(&s.port.to_be_bytes());
+ s.buf_len = 7 + host_len;
+ s.buf_sent = 0;
+ s.stage = SocksStage::Cmd;
+ s.io_state = SocksIOState::Writing;
+ return rexmpp_socks_proceed(s);
+ }
+ } else if s.stage == SocksStage::Cmd {
+ if s.buf_len >= 5 {
+ let mut full_len : usize = 6;
+ match s.buf[3] {
+ // IPv4
+ 1 => full_len += 4,
+ // Domain name
+ 3 => full_len += usize::from(s.buf[4]) + 1,
+ // IPv6
+ 4 => full_len += 16,
+ _ => return SocksErr::EReply
+ }
+ if s.buf_len > full_len {
+ return SocksErr::EReply;
+ }
+ if s.buf_len == full_len {
+ if s.socks_error != 0 {
+ return SocksErr::ESocks;
+ }
+ // We are done
+ s.stage = SocksStage::Done;
+ return SocksErr::Connected;
+ }
+ }
+ }
+ }
+ Err(error) => match error.kind() {
+ ErrorKind::WouldBlock => return SocksErr::EAgain,
+ _ => return SocksErr::ETCP
+ }
+ }
+ }
+ return SocksErr::EAgain
+}
+
+#[no_mangle]
+extern "C" fn rexmpp_socks_init (
+ s : &mut RexmppSocks,
+ fd: c_int,
+ host: *const c_char,
+ port: u16
+)
+ -> SocksErr
+{
+ s.fd = fd;
+ s.host = host;
+ s.port = port;
+ s.socks_error = 0;
+
+ let host_cstr : &CStr = unsafe { CStr::from_ptr(host) };
+ if host_cstr.to_bytes().len() > 255 {
+ return SocksErr::EHost;
+ }
+
+ // Request authentication
+ s.stage = SocksStage::Auth;
+ s.io_state = SocksIOState::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);
+}