From f0f947ac56f58ac5e047bdcafcdc8b9a1a1e34ee Mon Sep 17 00:00:00 2001 From: defanor Date: Thu, 4 May 2023 21:29:28 +0300 Subject: 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. --- README | 3 +- configure.ac | 9 +++ src/Makefile.am | 19 +++++- src/rexmpp_socks.rs | 180 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 208 insertions(+), 3 deletions(-) create mode 100644 src/rexmpp_socks.rs 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); +} -- cgit v1.2.3