diff options
Diffstat (limited to 'src/rexmpp_socks.rs')
-rw-r--r-- | src/rexmpp_socks.rs | 180 |
1 files changed, 180 insertions, 0 deletions
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); +} |