wrac compatibility (no tests)

This commit is contained in:
MeexReay 2025-04-21 21:24:41 +03:00
parent 06c27aac63
commit 73f7c565e1
12 changed files with 544 additions and 190 deletions

138
Cargo.lock generated
View File

@ -106,6 +106,7 @@ dependencies = [
"serde_default", "serde_default",
"serde_yml", "serde_yml",
"socks", "socks",
"tungstenite",
"winapi", "winapi",
"winresource", "winresource",
] ]
@ -122,6 +123,15 @@ version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.17.0" version = "3.17.0"
@ -134,6 +144,12 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]] [[package]]
name = "cairo-rs" name = "cairo-rs"
version = "0.20.7" version = "0.20.7"
@ -264,6 +280,25 @@ version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "cpufeatures"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [
"libc",
]
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]] [[package]]
name = "darling" name = "darling"
version = "0.20.11" version = "0.20.11"
@ -299,6 +334,22 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "data-encoding"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]] [[package]]
name = "equivalent" name = "equivalent"
version = "1.0.1" version = "1.0.1"
@ -499,6 +550,16 @@ dependencies = [
"system-deps", "system-deps",
] ]
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.3.1" version = "0.3.1"
@ -775,6 +836,23 @@ dependencies = [
"windows", "windows",
] ]
[[package]]
name = "http"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
dependencies = [
"bytes",
"fnv",
"itoa",
]
[[package]]
name = "httparse"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
[[package]] [[package]]
name = "iana-time-zone" name = "iana-time-zone"
version = "0.1.63" version = "0.1.63"
@ -1267,6 +1345,17 @@ dependencies = [
"version_check", "version_check",
] ]
[[package]]
name = "sha1"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]] [[package]]
name = "shlex" name = "shlex"
version = "1.3.0" version = "1.3.0"
@ -1349,6 +1438,26 @@ dependencies = [
"windows-sys", "windows-sys",
] ]
[[package]]
name = "thiserror"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "toml" name = "toml"
version = "0.8.20" version = "0.8.20"
@ -1383,12 +1492,41 @@ dependencies = [
"winnow", "winnow",
] ]
[[package]]
name = "tungstenite"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13"
dependencies = [
"bytes",
"data-encoding",
"http",
"httparse",
"log",
"rand",
"sha1",
"thiserror",
"utf-8",
]
[[package]]
name = "typenum"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.16" version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
[[package]]
name = "utf-8"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]] [[package]]
name = "utf8parse" name = "utf8parse"
version = "0.2.2" version = "0.2.2"

View File

@ -19,6 +19,7 @@ socks = "0.3.4"
libnotify = { version = "1.0.3", optional = true } libnotify = { version = "1.0.3", optional = true }
gdk-pixbuf = { version = "0.3.0", optional = true } gdk-pixbuf = { version = "0.3.0", optional = true }
winapi = { version = "0.3.9", optional = true, features = ["wincon", "winuser"] } winapi = { version = "0.3.9", optional = true, features = ["wincon", "winuser"] }
tungstenite = "0.26.2"
[features] [features]
default = [] default = []

View File

@ -26,6 +26,7 @@ pub struct Config {
#[serde(default = "default_true")] pub chunked_enabled: bool, #[serde(default = "default_true")] pub chunked_enabled: bool,
#[serde(default = "default_true")] pub formatting_enabled: bool, #[serde(default = "default_true")] pub formatting_enabled: bool,
#[serde(default = "default_true")] pub commands_enabled: bool, #[serde(default = "default_true")] pub commands_enabled: bool,
#[serde(default)] pub wrac_enabled: bool,
#[serde(default)] pub proxy: Option<String>, #[serde(default)] pub proxy: Option<String>,
#[serde(default = "default_true")] pub notifications_enabled: bool, #[serde(default = "default_true")] pub notifications_enabled: bool,
} }
@ -117,6 +118,7 @@ pub struct Args {
#[arg(long)] pub formatting_enabled: Option<bool>, #[arg(long)] pub formatting_enabled: Option<bool>,
#[arg(long)] pub commands_enabled: Option<bool>, #[arg(long)] pub commands_enabled: Option<bool>,
#[arg(long)] pub notifications_enabled: Option<bool>, #[arg(long)] pub notifications_enabled: Option<bool>,
#[arg(long)] pub wrac_enabled: Option<bool>,
#[arg(long)] pub proxy: Option<String>, #[arg(long)] pub proxy: Option<String>,
} }
@ -136,5 +138,6 @@ impl Args {
if let Some(v) = self.formatting_enabled { config.formatting_enabled = v } if let Some(v) = self.formatting_enabled { config.formatting_enabled = v }
if let Some(v) = self.commands_enabled { config.commands_enabled = v } if let Some(v) = self.commands_enabled { config.commands_enabled = v }
if let Some(v) = self.notifications_enabled { config.notifications_enabled = v } if let Some(v) = self.notifications_enabled { config.notifications_enabled = v }
if let Some(v) = self.wrac_enabled { config.wrac_enabled = v }
} }
} }

View File

@ -77,7 +77,8 @@ macro_rules! connect_rac {
&mut connect( &mut connect(
&$ctx.config(|o| o.host.clone()), &$ctx.config(|o| o.host.clone()),
$ctx.config(|o| o.ssl_enabled), $ctx.config(|o| o.ssl_enabled),
$ctx.config(|o| o.proxy.clone()) $ctx.config(|o| o.proxy.clone()),
$ctx.config(|o| o.wrac_enabled)
)? )?
}; };
} }

View File

@ -165,6 +165,7 @@ fn open_settings(ctx: Arc<Context>, app: &Application) {
let show_other_ip_entry = gui_checkbox_setting!("Show Other IP", show_other_ip, ctx, vbox); let show_other_ip_entry = gui_checkbox_setting!("Show Other IP", show_other_ip, ctx, vbox);
let auth_enabled_entry = gui_checkbox_setting!("Fake Auth Enabled", auth_enabled, ctx, vbox); let auth_enabled_entry = gui_checkbox_setting!("Fake Auth Enabled", auth_enabled, ctx, vbox);
let ssl_enabled_entry = gui_checkbox_setting!("SSL Enabled", ssl_enabled, ctx, vbox); let ssl_enabled_entry = gui_checkbox_setting!("SSL Enabled", ssl_enabled, ctx, vbox);
let wrac_enabled_entry = gui_checkbox_setting!("WRAC Enabled", wrac_enabled, ctx, vbox);
let chunked_enabled_entry = gui_checkbox_setting!("Chunked Enabled", chunked_enabled, ctx, vbox); let chunked_enabled_entry = gui_checkbox_setting!("Chunked Enabled", chunked_enabled, ctx, vbox);
let formatting_enabled_entry = gui_checkbox_setting!("Formatting Enabled", formatting_enabled, ctx, vbox); let formatting_enabled_entry = gui_checkbox_setting!("Formatting Enabled", formatting_enabled, ctx, vbox);
let commands_enabled_entry = gui_checkbox_setting!("Commands Enabled", commands_enabled, ctx, vbox); let commands_enabled_entry = gui_checkbox_setting!("Commands Enabled", commands_enabled, ctx, vbox);
@ -191,6 +192,7 @@ fn open_settings(ctx: Arc<Context>, app: &Application) {
#[weak] formatting_enabled_entry, #[weak] formatting_enabled_entry,
#[weak] commands_enabled_entry, #[weak] commands_enabled_entry,
#[weak] notifications_enabled_entry, #[weak] notifications_enabled_entry,
#[weak] wrac_enabled_entry,
#[weak] proxy_entry, #[weak] proxy_entry,
move |_| { move |_| {
let config = Config { let config = Config {
@ -231,6 +233,7 @@ fn open_settings(ctx: Arc<Context>, app: &Application) {
show_other_ip: show_other_ip_entry.is_active(), show_other_ip: show_other_ip_entry.is_active(),
auth_enabled: auth_enabled_entry.is_active(), auth_enabled: auth_enabled_entry.is_active(),
ssl_enabled: ssl_enabled_entry.is_active(), ssl_enabled: ssl_enabled_entry.is_active(),
wrac_enabled: wrac_enabled_entry.is_active(),
chunked_enabled: chunked_enabled_entry.is_active(), chunked_enabled: chunked_enabled_entry.is_active(),
formatting_enabled: formatting_enabled_entry.is_active(), formatting_enabled: formatting_enabled_entry.is_active(),
commands_enabled: commands_enabled_entry.is_active(), commands_enabled: commands_enabled_entry.is_active(),
@ -267,6 +270,7 @@ fn open_settings(ctx: Arc<Context>, app: &Application) {
#[weak] show_other_ip_entry, #[weak] show_other_ip_entry,
#[weak] auth_enabled_entry, #[weak] auth_enabled_entry,
#[weak] ssl_enabled_entry, #[weak] ssl_enabled_entry,
#[weak] wrac_enabled_entry,
#[weak] chunked_enabled_entry, #[weak] chunked_enabled_entry,
#[weak] formatting_enabled_entry, #[weak] formatting_enabled_entry,
#[weak] commands_enabled_entry, #[weak] commands_enabled_entry,
@ -286,6 +290,7 @@ fn open_settings(ctx: Arc<Context>, app: &Application) {
show_other_ip_entry.set_active(config.show_other_ip); show_other_ip_entry.set_active(config.show_other_ip);
auth_enabled_entry.set_active(config.auth_enabled); auth_enabled_entry.set_active(config.auth_enabled);
ssl_enabled_entry.set_active(config.ssl_enabled); ssl_enabled_entry.set_active(config.ssl_enabled);
wrac_enabled_entry.set_active(config.wrac_enabled);
chunked_enabled_entry.set_active(config.chunked_enabled); chunked_enabled_entry.set_active(config.chunked_enabled);
formatting_enabled_entry.set_active(config.formatting_enabled); formatting_enabled_entry.set_active(config.formatting_enabled);
commands_enabled_entry.set_active(config.commands_enabled); commands_enabled_entry.set_active(config.commands_enabled);

View File

@ -2,12 +2,9 @@ use std::{
error::Error, sync::Arc, thread, time::{Duration, SystemTime, UNIX_EPOCH} error::Error, sync::Arc, thread, time::{Duration, SystemTime, UNIX_EPOCH}
}; };
use crate::{connect_rac, proto::{register_user, send_message_auth}}; use crate::connect_rac;
use super::{ use super::proto::{connect, read_messages, send_message, send_message_spoof_auth, register_user, send_message_auth};
proto::{connect, read_messages, send_message, send_message_spoof_auth},
util::sanitize_text
};
use gui::{add_chat_message, clear_chat_messages}; use gui::{add_chat_message, clear_chat_messages};
use lazy_static::lazy_static; use lazy_static::lazy_static;
@ -19,6 +16,9 @@ pub use gui::run_main_loop;
lazy_static! { lazy_static! {
static ref ANSI_REGEX: Regex = Regex::new(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])").unwrap();
static ref CONTROL_CHARS_REGEX: Regex = Regex::new(r"[\x00-\x1F\x7F]").unwrap();
pub static ref DATE_REGEX: Regex = Regex::new(r"\[(.*?)\] (.*)").unwrap(); pub static ref DATE_REGEX: Regex = Regex::new(r"\[(.*?)\] (.*)").unwrap();
pub static ref IP_REGEX: Regex = Regex::new(r"\{(.*?)\} (.*)").unwrap(); pub static ref IP_REGEX: Regex = Regex::new(r"\{(.*?)\} (.*)").unwrap();
@ -44,6 +44,11 @@ const HELP_MESSAGE: &str = "Help message:
/spam n text - send message with text n times /spam n text - send message with text n times
/ping - check server ping"; /ping - check server ping";
pub fn sanitize_text(input: &str) -> String {
let without_ansi = ANSI_REGEX.replace_all(input, "");
let cleaned_text = CONTROL_CHARS_REGEX.replace_all(&without_ansi, "");
cleaned_text.into_owned()
}
pub fn add_message(ctx: Arc<Context>, message: &str) -> Result<(), Box<dyn Error>> { pub fn add_message(ctx: Arc<Context>, message: &str) -> Result<(), Box<dyn Error>> {
for i in message.split("\n") for i in message.split("\n")

View File

@ -1,5 +1,4 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
pub mod chat; pub mod chat;
pub mod util;
pub mod proto; pub mod proto;

View File

@ -20,7 +20,7 @@ fn main() {
let mut config = load_config(config_path); let mut config = load_config(config_path);
if args.read_messages { if args.read_messages {
let mut stream = connect(&config.host, config.ssl_enabled, config.proxy.clone()).expect("Error reading message"); let mut stream = connect(&config.host, config.ssl_enabled, config.proxy.clone(), config.wrac_enabled).expect("Error reading message");
print!("{}", read_messages( print!("{}", read_messages(
&mut stream, &mut stream,
@ -35,7 +35,7 @@ fn main() {
} }
if let Some(message) = &args.send_message { if let Some(message) = &args.send_message {
let mut stream = connect(&config.host, config.ssl_enabled, config.proxy.clone()).expect("Error sending message"); let mut stream = connect(&config.host, config.ssl_enabled, config.proxy.clone(), config.wrac_enabled).expect("Error sending message");
send_message( send_message(
&mut stream, &mut stream,

View File

@ -1,49 +1,72 @@
#![allow(unused)] use std::{error::Error, fmt::Debug, io::{Read, Write}, net::{TcpStream, ToSocketAddrs}, time::Duration};
use std::{error::Error, fmt::Debug, io::{Read, Write}, net::{SocketAddr, TcpStream, ToSocketAddrs}, str::FromStr, time::Duration};
use native_tls::{TlsConnector, TlsStream}; use native_tls::{TlsConnector, TlsStream};
use socks::Socks5Stream; use socks::Socks5Stream;
use tungstenite::WebSocket;
use crate::util::parse_socks5_url; pub mod rac;
pub mod wrac;
pub trait RacStream: Read + Write + Unpin + Send + Sync + Debug { pub trait Stream: Read + Write + Unpin + Send + Sync + Debug {
fn set_read_timeout(&self, timeout: Duration); fn set_read_timeout(&self, timeout: Duration);
fn set_write_timeout(&self, timeout: Duration); fn set_write_timeout(&self, timeout: Duration);
} }
impl RacStream for TcpStream { impl Stream for TcpStream {
fn set_read_timeout(&self, timeout: Duration) { let _ = TcpStream::set_read_timeout(&self, Some(timeout)); } fn set_read_timeout(&self, timeout: Duration) { let _ = TcpStream::set_read_timeout(&self, Some(timeout)); }
fn set_write_timeout(&self, timeout: Duration) { let _ = TcpStream::set_write_timeout(&self, Some(timeout)); } fn set_write_timeout(&self, timeout: Duration) { let _ = TcpStream::set_write_timeout(&self, Some(timeout)); }
} }
impl RacStream for Socks5Stream { impl Stream for Socks5Stream {
fn set_read_timeout(&self, timeout: Duration) { let _ = TcpStream::set_read_timeout(self.get_ref(), Some(timeout)); } fn set_read_timeout(&self, timeout: Duration) { let _ = TcpStream::set_read_timeout(self.get_ref(), Some(timeout)); }
fn set_write_timeout(&self, timeout: Duration) { let _ = TcpStream::set_write_timeout(self.get_ref(), Some(timeout)); } fn set_write_timeout(&self, timeout: Duration) { let _ = TcpStream::set_write_timeout(self.get_ref(), Some(timeout)); }
} }
impl<T: RacStream> RacStream for TlsStream<T> { impl<T: Stream> Stream for TlsStream<T> {
fn set_read_timeout(&self, timeout: Duration) { self.get_ref().set_read_timeout(timeout); } fn set_read_timeout(&self, timeout: Duration) { self.get_ref().set_read_timeout(timeout); }
fn set_write_timeout(&self, timeout: Duration) { self.get_ref().set_write_timeout(timeout); } fn set_write_timeout(&self, timeout: Duration) { self.get_ref().set_write_timeout(timeout); }
} }
impl RacStream for TlsStream<Box<dyn RacStream>> { impl Stream for TlsStream<Box<dyn Stream>> {
fn set_read_timeout(&self, timeout: Duration) { self.get_ref().set_read_timeout(timeout); } fn set_read_timeout(&self, timeout: Duration) { self.get_ref().set_read_timeout(timeout); }
fn set_write_timeout(&self, timeout: Duration) { self.get_ref().set_write_timeout(timeout); } fn set_write_timeout(&self, timeout: Duration) { self.get_ref().set_write_timeout(timeout); }
} }
pub enum RacStream {
WRAC(WebSocket<Box<dyn Stream>>),
RAC(Box<dyn Stream>)
}
/// `socks5://user:pass@127.0.0.1:12345/path -> ("127.0.0.1:12345", ("user", "pass"))` \
/// `socks5://127.0.0.1:12345 -> ("127.0.0.1:12345", None)` \
/// `https://127.0.0.1:12345 -> ("127.0.0.1:12345", None)` \
/// `127.0.0.1:12345 -> ("127.0.0.1:12345", None)` \
/// `user:pass@127.0.0.1:12345 -> ("127.0.0.1:12345", ("user", "pass"))`
pub fn parse_socks5_url(url: &str) -> Option<(String, Option<(String, String)>)> {
let (_, url) = url.split_once("://").unwrap_or(("", url));
let (url, _) = url.split_once("/").unwrap_or((url, ""));
if let Some((auth, url)) = url.split_once("@") {
let (user, pass) = auth.split_once(":")?;
Some((url.to_string(), Some((user.to_string(), pass.to_string()))))
} else {
Some((url.to_string(), None))
}
}
/// Create RAC connection (also you can just TcpStream::connect) /// Create RAC connection (also you can just TcpStream::connect)
/// ///
/// host - host string, example: "example.com:12345", "example.com" (default port is 42666) /// host - host string, example: "example.com:12345", "example.com" (default port is 42666)
/// ssl - wrap with ssl client, write false if you dont know what it is /// ssl - wrap with ssl client, write false if you dont know what it is
/// proxy - socks5 proxy (host, (user, pass)) /// proxy - socks5 proxy (host, (user, pass))
pub fn connect(host: &str, ssl: bool, proxy: Option<String>) -> Result<Box<dyn RacStream>, Box<dyn Error>> { /// wrac - to use wrac protocol
pub fn connect(host: &str, ssl: bool, proxy: Option<String>, wrac: bool) -> Result<RacStream, Box<dyn Error>> {
let host = if host.contains(":") { let host = if host.contains(":") {
host.to_string() host.to_string()
} else { } else {
format!("{host}:42666") format!("{host}:42666")
}; };
let stream: Box<dyn RacStream> = if let Some(proxy) = proxy { let stream: Box<dyn Stream> = if let Some(proxy) = proxy {
if let Some((proxy, auth)) = parse_socks5_url(&proxy) { if let Some((proxy, auth)) = parse_socks5_url(&proxy) {
if let Some((user, pass)) = auth { if let Some((user, pass)) = auth {
Box::new(Socks5Stream::connect_with_password(&proxy, host.as_str(), &user, &pass)?) Box::new(Socks5Stream::connect_with_password(&proxy, host.as_str(), &user, &pass)?)
@ -76,16 +99,50 @@ pub fn connect(host: &str, ssl: bool, proxy: Option<String>) -> Result<Box<dyn R
stream.set_read_timeout(Duration::from_secs(3)); stream.set_read_timeout(Duration::from_secs(3));
stream.set_write_timeout(Duration::from_secs(3)); stream.set_write_timeout(Duration::from_secs(3));
Ok(stream) if wrac {
Ok(RacStream::WRAC(tungstenite::accept(stream)?))
} else {
Ok(RacStream::RAC(stream))
}
} }
/// Send message with fake auth
///
/// Explaination:
///
/// let (name, message) = message.split("> ") else { return send_message(stream, message) }
/// if send_message_auth(name, name, message) != 0 {
/// let name = "\x1f" + name
/// register_user(stream, name, name)
/// send_message_spoof_auth(stream, name + "> " + message)
/// }
pub fn send_message_spoof_auth(stream: &mut RacStream, message: &str, remove_null: bool) -> Result<(), Box<dyn Error>> {
let Some((name, message)) = message.split_once("> ") else { return send_message(stream, message) };
if let Ok(f) = send_message_auth(stream, &name, &message, &message, remove_null) {
if f != 0 {
let name = format!("\x1f{name}");
register_user(stream, &name, &name, remove_null)?;
send_message_spoof_auth(stream, &format!("{name}> {message}"), remove_null)?;
}
}
Ok(())
}
/// Send message /// Send message
/// ///
/// stream - any stream that can be written to /// stream - any stream that can be written to
/// message - message text /// message - message text
pub fn send_message(stream: &mut impl Write, message: &str) -> Result<(), Box<dyn Error>> { pub fn send_message(
stream.write_all(format!("\x01{message}").as_bytes())?; stream: &mut RacStream,
Ok(()) message: &str
) -> Result<(), Box<dyn Error>> {
match stream {
RacStream::WRAC(websocket) => wrac::send_message(websocket, message),
RacStream::RAC(stream) => rac::send_message(stream, message)
}
} }
/// Register user /// Register user
@ -97,25 +154,14 @@ pub fn send_message(stream: &mut impl Write, message: &str) -> Result<(), Box<dy
/// ///
/// returns whether the user was registered /// returns whether the user was registered
pub fn register_user( pub fn register_user(
stream: &mut (impl Write + Read), stream: &mut RacStream,
name: &str, name: &str,
password: &str, password: &str,
remove_null: bool remove_null: bool
) -> Result<bool, Box<dyn Error>> { ) -> Result<bool, Box<dyn Error>> {
stream.write_all(format!("\x03{name}\n{password}").as_bytes())?; match stream {
if remove_null { RacStream::WRAC(websocket) => wrac::register_user(websocket, name, password),
if let Ok(out) = skip_null(stream) { RacStream::RAC(stream) => rac::register_user(stream, name, password, remove_null)
Ok(out[0] == 0)
} else {
Ok(true)
}
} else {
let mut buf = vec![0];
if let Ok(1) = stream.read(&mut buf) {
Ok(buf[0] == 0)
} else {
Ok(true)
}
} }
} }
@ -131,71 +177,16 @@ pub fn register_user(
/// returns 1 if the user does not exist /// returns 1 if the user does not exist
/// returns 2 if the password is incorrect /// returns 2 if the password is incorrect
pub fn send_message_auth( pub fn send_message_auth(
stream: &mut (impl Write + Read), stream: &mut RacStream,
name: &str, name: &str,
password: &str, password: &str,
message: &str, message: &str,
remove_null: bool remove_null: bool
) -> Result<u8, Box<dyn Error>> { ) -> Result<u8, Box<dyn Error>> {
stream.write_all(format!("\x02{name}\n{password}\n{message}").as_bytes())?; match stream {
RacStream::WRAC(websocket) => wrac::send_message_auth(websocket, name, password, message),
if remove_null { RacStream::RAC(stream) => rac::send_message_auth(stream, name, password, message, remove_null)
if let Ok(out) = skip_null(stream) {
Ok(out[0])
} else {
Ok(0)
} }
} else {
let mut buf = vec![0];
if let Ok(1) = stream.read(&mut buf) {
Ok(buf[0])
} else {
Ok(0)
}
}
}
/// Send message with fake auth
///
/// Explaination:
///
/// let (name, message) = message.split("> ") else { return send_message(stream, message) }
/// if send_message_auth(name, name, message) != 0 {
/// let name = "\x1f" + name
/// register_user(stream, name, name)
/// send_message_spoof_auth(stream, name + "> " + message)
/// }
pub fn send_message_spoof_auth(stream: &mut (impl Write + Read), message: &str, remove_null: bool) -> Result<(), Box<dyn Error>> {
let Some((name, message)) = message.split_once("> ") else { return send_message(stream, message) };
if let Ok(f) = send_message_auth(stream, &name, &message, &message, remove_null) {
if f != 0 {
let name = format!("\x1f{name}");
register_user(stream, &name, &name, remove_null);
send_message_spoof_auth(stream, &format!("{name}> {message}"), remove_null);
}
}
Ok(())
}
/// Skip null bytes and return first non-null byte
pub fn skip_null(stream: &mut impl Read) -> Result<Vec<u8>, Box<dyn Error>> {
loop {
let mut buf = vec![0; 1];
stream.read_exact(&mut buf)?;
if buf[0] != 0 {
break Ok(buf)
}
}
}
/// remove trailing null bytes in vector
pub fn remove_trailing_null(vec: &mut Vec<u8>) -> Result<(), Box<dyn Error>> {
while vec.ends_with(&[0]) {
vec.remove(vec.len()-1);
}
Ok(())
} }
/// Read messages /// Read messages
@ -207,66 +198,14 @@ pub fn remove_trailing_null(vec: &mut Vec<u8>) -> Result<(), Box<dyn Error>> {
/// ///
/// returns (messages, packet size) /// returns (messages, packet size)
pub fn read_messages( pub fn read_messages(
stream: &mut (impl Read + Write), stream: &mut RacStream,
max_messages: usize, max_messages: usize,
last_size: usize, last_size: usize,
remove_null: bool, remove_null: bool,
chunked: bool chunked: bool
) -> Result<Option<(Vec<String>, usize)>, Box<dyn Error>> { ) -> Result<Option<(Vec<String>, usize)>, Box<dyn Error>> {
stream.write_all(&[0x00])?; match stream {
RacStream::WRAC(websocket) => wrac::read_messages(websocket, max_messages, last_size, chunked),
let packet_size = { RacStream::RAC(stream) => rac::read_messages(stream, max_messages, last_size, remove_null, chunked)
let data = if remove_null {
let mut data = skip_null(stream)?;
let mut buf = vec![0; 10];
let len = stream.read(&mut buf)?;
buf.truncate(len);
data.append(&mut buf);
remove_trailing_null(&mut data)?;
data
} else {
let mut data = vec![0; 10];
let len = stream.read(&mut data)?;
data.truncate(len);
data
};
String::from_utf8(data)?
.trim_matches(char::from(0))
.parse()?
};
if last_size == packet_size {
return Ok(None);
} }
let to_read = if !chunked || last_size == 0 {
stream.write_all(&[0x01])?;
packet_size
} else {
stream.write_all(format!("\x02{}", last_size).as_bytes())?;
packet_size - last_size
};
let packet_data = if remove_null {
let mut data = skip_null(stream)?;
let mut buf = vec![0; to_read - 1];
stream.read_exact(&mut buf)?;
data.append(&mut buf);
data
} else {
let mut data = vec![0; to_read];
stream.read_exact(&mut data)?;
data
};
let packet_data = String::from_utf8_lossy(&packet_data).to_string();
let lines: Vec<&str> = packet_data.split("\n").collect();
let lines: Vec<String> = lines.clone().into_iter()
.skip(if lines.len() >= max_messages { lines.len() - max_messages } else { 0 })
.map(|o| o.to_string())
.collect();
Ok(Some((lines, packet_size)))
} }

169
src/proto/rac.rs Normal file
View File

@ -0,0 +1,169 @@
use std::{error::Error, io::{Read, Write}};
/// Send message
///
/// stream - any stream that can be written to
/// message - message text
pub fn send_message(stream: &mut impl Write, message: &str) -> Result<(), Box<dyn Error>> {
stream.write_all(format!("\x01{message}").as_bytes())?;
Ok(())
}
/// Register user
///
/// stream - any stream that can be written to
/// name - user name
/// password - user password
/// remove_null - remove null bytes on reading
///
/// returns whether the user was registered
pub fn register_user(
stream: &mut (impl Write + Read),
name: &str,
password: &str,
remove_null: bool
) -> Result<bool, Box<dyn Error>> {
stream.write_all(format!("\x03{name}\n{password}").as_bytes())?;
if remove_null {
if let Ok(out) = skip_null(stream) {
Ok(out[0] == 0)
} else {
Ok(true)
}
} else {
let mut buf = vec![0];
if let Ok(1) = stream.read(&mut buf) {
Ok(buf[0] == 0)
} else {
Ok(true)
}
}
}
/// Send message with auth
///
/// stream - any stream that can be written to
/// message - message text
/// name - user name
/// password - user password
/// remove_null - remove null bytes on reading
///
/// returns 0 if the message was sent successfully
/// returns 1 if the user does not exist
/// returns 2 if the password is incorrect
pub fn send_message_auth(
stream: &mut (impl Write + Read),
name: &str,
password: &str,
message: &str,
remove_null: bool
) -> Result<u8, Box<dyn Error>> {
stream.write_all(format!("\x02{name}\n{password}\n{message}").as_bytes())?;
if remove_null {
if let Ok(out) = skip_null(stream) {
Ok(out[0])
} else {
Ok(0)
}
} else {
let mut buf = vec![0];
if let Ok(1) = stream.read(&mut buf) {
Ok(buf[0])
} else {
Ok(0)
}
}
}
/// Skip null bytes and return first non-null byte
pub fn skip_null(stream: &mut impl Read) -> Result<Vec<u8>, Box<dyn Error>> {
loop {
let mut buf = vec![0; 1];
stream.read_exact(&mut buf)?;
if buf[0] != 0 {
break Ok(buf)
}
}
}
/// remove trailing null bytes in vector
pub fn remove_trailing_null(vec: &mut Vec<u8>) -> Result<(), Box<dyn Error>> {
while vec.ends_with(&[0]) {
vec.remove(vec.len()-1);
}
Ok(())
}
/// Read messages
///
/// max_messages - max messages in list
/// last_size - last returned packet size
/// remove_null - start with skipping null bytes
/// chunked - is chunked reading enabled
///
/// returns (messages, packet size)
pub fn read_messages(
stream: &mut (impl Read + Write),
max_messages: usize,
last_size: usize,
remove_null: bool,
chunked: bool
) -> Result<Option<(Vec<String>, usize)>, Box<dyn Error>> {
stream.write_all(&[0x00])?;
let packet_size = {
let data = if remove_null {
let mut data = skip_null(stream)?;
let mut buf = vec![0; 10];
let len = stream.read(&mut buf)?;
buf.truncate(len);
data.append(&mut buf);
remove_trailing_null(&mut data)?;
data
} else {
let mut data = vec![0; 10];
let len = stream.read(&mut data)?;
data.truncate(len);
data
};
String::from_utf8(data)?
.trim_matches(char::from(0))
.parse()?
};
if last_size == packet_size {
return Ok(None);
}
let to_read = if !chunked || last_size == 0 {
stream.write_all(&[0x01])?;
packet_size
} else {
stream.write_all(format!("\x02{}", last_size).as_bytes())?;
packet_size - last_size
};
let packet_data = if remove_null {
let mut data = skip_null(stream)?;
let mut buf = vec![0; to_read - 1];
stream.read_exact(&mut buf)?;
data.append(&mut buf);
data
} else {
let mut data = vec![0; to_read];
stream.read_exact(&mut data)?;
data
};
let packet_data = String::from_utf8_lossy(&packet_data).to_string();
let lines: Vec<&str> = packet_data.split("\n").collect();
let lines: Vec<String> = lines.clone().into_iter()
.skip(if lines.len() >= max_messages { lines.len() - max_messages } else { 0 })
.map(|o| o.to_string())
.collect();
Ok(Some((lines, packet_size)))
}

123
src/proto/wrac.rs Normal file
View File

@ -0,0 +1,123 @@
use std::{error::Error, io::{Read, Write}};
use tungstenite::{WebSocket, Message};
/// Send message
///
/// stream - any stream that can be written to
/// message - message text
pub fn send_message(
stream: &mut WebSocket<impl Write + Read>,
message: &str
) -> Result<(), Box<dyn Error>> {
stream.write(Message::Binary(format!("\x01{message}").as_bytes().to_vec().into()))?;
Ok(())
}
/// Register user
///
/// stream - any stream that can be written to
/// name - user name
/// password - user password
///
/// returns whether the user was registered
pub fn register_user(
stream: &mut WebSocket<impl Write + Read>,
name: &str,
password: &str
) -> Result<bool, Box<dyn Error>> {
stream.write(Message::Binary(format!("\x03{name}\n{password}").as_bytes().to_vec().into()))?;
if let Ok(msg) = stream.read() {
Ok(!msg.is_binary() || msg.into_data().get(0).unwrap_or(&0) == &0)
} else {
Ok(true)
}
}
/// Send message with auth
///
/// stream - any stream that can be written to
/// message - message text
/// name - user name
/// password - user password
///
/// returns 0 if the message was sent successfully
/// returns 1 if the user does not exist
/// returns 2 if the password is incorrect
pub fn send_message_auth(
stream: &mut WebSocket<impl Write + Read>,
name: &str,
password: &str,
message: &str
) -> Result<u8, Box<dyn Error>> {
stream.write(Message::Binary(format!("\x02{name}\n{password}\n{message}").as_bytes().to_vec().into()))?;
if let Ok(msg) = stream.read() {
if msg.is_binary() {
Ok(0)
} else {
Ok(*msg.into_data().get(0).unwrap_or(&0))
}
} else {
Ok(0)
}
}
/// Read messages
///
/// max_messages - max messages in list
/// last_size - last returned packet size
/// chunked - is chunked reading enabled
///
/// returns (messages, packet size)
pub fn read_messages(
stream: &mut WebSocket<impl Write + Read>,
max_messages: usize,
last_size: usize,
chunked: bool
) -> Result<Option<(Vec<String>, usize)>, Box<dyn Error>> {
stream.write(Message::Binary(vec![0x00].into()))?;
let packet_size = {
let msg = stream.read()?;
if !msg.is_binary() {
return Err("msg is not binary".into());
}
let len = msg.into_data().to_vec();
String::from_utf8(len)?
.trim_matches(char::from(0))
.parse()?
};
if last_size == packet_size {
return Ok(None);
}
let to_read = if !chunked || last_size == 0 {
stream.write(Message::Binary(vec![0x00, 0x01].into()))?;
packet_size
} else {
stream.write(Message::Binary(format!("\x00\x02{}", last_size).as_bytes().to_vec().into()))?;
packet_size - last_size
};
let msg = stream.read()?;
if !msg.is_binary() {
return Err("msg is not binary".into());
}
let packet_data = msg.into_data().to_vec();
if packet_data.len() > to_read {
return Err("too big msg".into());
}
let packet_data = String::from_utf8_lossy(&packet_data).to_string();
let lines: Vec<&str> = packet_data.split("\n").collect();
let lines: Vec<String> = lines.clone().into_iter()
.skip(if lines.len() >= max_messages { lines.len() - max_messages } else { 0 })
.map(|o| o.to_string())
.collect();
Ok(Some((lines, packet_size)))
}

View File

@ -1,29 +0,0 @@
use lazy_static::lazy_static;
use regex::Regex;
lazy_static! {
static ref ANSI_REGEX: Regex = Regex::new(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])").unwrap();
static ref CONTROL_CHARS_REGEX: Regex = Regex::new(r"[\x00-\x1F\x7F]").unwrap();
}
pub fn sanitize_text(input: &str) -> String {
let without_ansi = ANSI_REGEX.replace_all(input, "");
let cleaned_text = CONTROL_CHARS_REGEX.replace_all(&without_ansi, "");
cleaned_text.into_owned()
}
/// `socks5://user:pass@127.0.0.1:12345/path -> ("127.0.0.1:12345", ("user", "pass"))` \
/// `socks5://127.0.0.1:12345 -> ("127.0.0.1:12345", None)` \
/// `https://127.0.0.1:12345 -> ("127.0.0.1:12345", None)` \
/// `127.0.0.1:12345 -> ("127.0.0.1:12345", None)` \
/// `user:pass@127.0.0.1:12345 -> ("127.0.0.1:12345", ("user", "pass"))`
pub fn parse_socks5_url(url: &str) -> Option<(String, Option<(String, String)>)> {
let (_, url) = url.split_once("://").unwrap_or(("", url));
let (url, _) = url.split_once("/").unwrap_or((url, ""));
if let Some((auth, url)) = url.split_once("@") {
let (user, pass) = auth.split_once(":")?;
Some((url.to_string(), Some((user.to_string(), pass.to_string()))))
} else {
Some((url.to_string(), None))
}
}