diff --git a/Cargo.lock b/Cargo.lock index 9af8587..7bc4f60 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -82,6 +82,7 @@ dependencies = [ "crossterm", "homedir", "lazy_static", + "native-tls", "rand", "regex", "serde", @@ -100,6 +101,15 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "cc" +version = "1.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7777341816418c02e033934a09f20dc0ccaf65a5201ef8a450ae0105a573fda" +dependencies = [ + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -114,9 +124,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "clap" -version = "4.5.28" +version = "4.5.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e77c3243bd94243c03672cb5154667347c457ca271254724f9f393aee1c05ff" +checksum = "8acebd8ad879283633b343856142139f2da2317c96b05b4dd6181c61e2480184" dependencies = [ "clap_builder", "clap_derive", @@ -124,9 +134,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.27" +version = "4.5.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7" +checksum = "f6ba32cbda51c7e1dfd49acc1457ba1a7dec5b64fe360e828acb13ca8dc9c2f9" dependencies = [ "anstream", "anstyle", @@ -167,6 +177,22 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "crossterm" version = "0.28.1" @@ -208,6 +234,27 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "getrandom" version = "0.3.1" @@ -328,6 +375,23 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "native-tls" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dab59f8e050d5df8e4dd87d9206fb6f65a483e20ac9fda365ade4fab353196c" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nix" version = "0.29.0" @@ -346,6 +410,50 @@ version = "1.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" +[[package]] +name = "openssl" +version = "0.10.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61cfb4e166a8bb8c9b55c500bc2308550148ece889be90f609377e58140f42c6" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b22d5b84be05a8d6947c7cb71f7c849aa0f112acd4bf51c2a7c1c988ac0a9dc" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "parking_lot" version = "0.12.3" @@ -369,6 +477,12 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + [[package]] name = "ppv-lite86" version = "0.2.20" @@ -484,12 +598,44 @@ version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "serde" version = "1.0.217" @@ -525,6 +671,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook" version = "0.3.17" @@ -578,6 +730,20 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" +dependencies = [ + "cfg-if", + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + [[package]] name = "unicode-ident" version = "1.0.16" @@ -590,6 +756,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" diff --git a/Cargo.toml b/Cargo.toml index 1af2a52..52bc13d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,4 +12,5 @@ crossterm = "0.28.1" serde = { version = "1.0.217", features = ["serde_derive"] } serde_yml = "0.0.12" homedir = "0.3.4" -clap = { version = "4.5.28", features = ["derive"] } +clap = { version = "4.5.29", features = ["derive"] } +native-tls = "0.2.13" \ No newline at end of file diff --git a/src/chat.rs b/src/chat.rs index d6d34a6..69007e2 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -4,7 +4,7 @@ use colored::{Color, Colorize}; use crossterm::{cursor::{MoveLeft, MoveRight}, event::{self, Event, KeyCode, KeyModifiers, MouseEventKind}, execute, terminal::{self, disable_raw_mode, enable_raw_mode}}; use rand::random; -use crate::{proto::send_message_auth, util::{char_index_to_byte_index, string_chunks}, IP_REGEX}; +use crate::{proto::{connect, send_message_auth}, util::{char_index_to_byte_index, string_chunks}, IP_REGEX}; use super::{proto::read_messages, util::sanitize_text, COLORED_USERNAMES, DATE_REGEX, config::Context, proto::send_message}; @@ -43,12 +43,12 @@ fn on_command(ctx: Arc, command: &str) -> Result<(), Box> { let args = args.split(" ").collect::>(); if command == "clear" { - send_message(&ctx.host, + send_message(&mut connect(&ctx.host, ctx.enable_ssl)?, &prepare_message(ctx.clone(), &format!("\r\x1B[1A{}", " ".repeat(64)).repeat(ctx.max_messages) ))?; } else if command == "spam" { - send_message(&ctx.host, + send_message(&mut connect(&ctx.host, ctx.enable_ssl)?, &prepare_message(ctx.clone(), &format!("\r\x1B[1A{}{}", args.join(" "), " ".repeat(10)).repeat(ctx.max_messages) ))?; @@ -65,9 +65,9 @@ Press enter to close")?; let mut before = ctx.messages.packet_size(); let start = SystemTime::now(); let message = format!("Checking ping... {:X}", random::()); - send_message(&ctx.host, &message)?; + send_message(&mut connect(&ctx.host, ctx.enable_ssl)?, &message)?; loop { - let data = read_messages(&ctx.host, ctx.max_messages, before).ok().flatten(); + let data = read_messages(&mut connect(&ctx.host, ctx.enable_ssl)?, ctx.max_messages, before, !ctx.enable_ssl).ok().flatten(); if let Some((data, size)) = data { if let Some(last) = data.iter().rev().find(|o| o.contains(&message)) { @@ -81,7 +81,7 @@ Press enter to close")?; } } } - send_message(&ctx.host, &format!("Ping = {}ms", start.elapsed().unwrap().as_millis()))?; + send_message(&mut connect(&ctx.host, ctx.enable_ssl)?, &format!("Ping = {}ms", start.elapsed().unwrap().as_millis()))?; } Ok(()) @@ -92,14 +92,22 @@ pub fn print_console(ctx: Arc, messages: Vec, input: &str) -> R let (width, height) = terminal::size()?; let (width, height) = (width as usize, height as usize); - let messages = messages + let mut messages = messages .into_iter() .flat_map(|o| string_chunks(&o, width as usize - 1)) .collect::>(); - let scroll = min(ctx.scroll.load(Ordering::SeqCst), messages.len()-height); - let scroll_f = ((1f64 - scroll as f64 / (messages.len()-height+1) as f64) * (height-2) as f64).round() as usize+1; + let messages_size = if messages.len() >= height { + messages.len()-height + } else { + for _ in 0..height-messages.len() { + messages.insert(0, (String::new(), 0)); + } + 0 + }; + let scroll = min(ctx.scroll.load(Ordering::SeqCst), messages_size); + let scroll_f = ((1f64 - scroll as f64 / (messages_size+1) as f64) * (height-2) as f64).round() as usize+1; let messages = if height < messages.len() { if scroll < messages.len() - height { @@ -169,7 +177,7 @@ fn prepare_message(context: Arc, message: &str) -> String { }, message, if !context.disable_hiding_ip { - let spaces = if context.auth { + let spaces = if context.enable_auth { 39 } else { 54 @@ -202,11 +210,15 @@ fn format_message(ctx: Arc, message: String) -> Option { (None, message) }; - let message = message.trim_start_matches("(UNREGISTERED)").trim().to_string()+" "; + let message = message + .trim_start_matches("(UNREGISTERED)") + .trim_start_matches("(UNAUTHORIZED)") + .trim() + .to_string()+" "; let prefix = if ctx.enable_ip_viewing { if let Some(ip) = ip { - format!("{}{} [{}]", ip, " ".repeat(15-ip.len()), date) + format!("{}{} [{}]", ip, " ".repeat(if 15 >= ip.chars().count() {15-ip.chars().count()} else {0}), date) } else { format!("{} [{}]", " ".repeat(15), date) } @@ -319,10 +331,10 @@ fn poll_events(ctx: Arc) -> Result<(), Box> { .replace("{text}", &message) ); - if ctx.auth { - send_message_auth(&ctx.host, &message)?; + if ctx.enable_auth { + send_message_auth(&mut connect(&ctx.host, ctx.enable_ssl)?, &message)?; } else { - send_message(&ctx.host, &message)?; + send_message(&mut connect(&ctx.host, ctx.enable_ssl)?, &message)?; } } } else { @@ -466,14 +478,20 @@ fn poll_events(ctx: Arc) -> Result<(), Box> { } pub fn recv_tick(ctx: Arc) -> Result<(), Box> { - if let Ok(Some((messages, size))) = read_messages(&ctx.host, ctx.max_messages, ctx.messages.packet_size()) { - let messages: Vec = if ctx.disable_formatting { - messages - } else { - messages.into_iter().flat_map(|o| format_message(ctx.clone(), o)).collect() - }; - ctx.messages.update(messages.clone(), size); - print_console(ctx.clone(), messages, &ctx.input.read().unwrap())?; + match read_messages(&mut connect(&ctx.host, ctx.enable_ssl)?, ctx.max_messages, ctx.messages.packet_size(), !ctx.enable_ssl) { + Ok(Some((messages, size))) => { + let messages: Vec = if ctx.disable_formatting { + messages + } else { + messages.into_iter().flat_map(|o| format_message(ctx.clone(), o)).collect() + }; + ctx.messages.update(messages.clone(), size); + print_console(ctx.clone(), messages, &ctx.input.read().unwrap())?; + } + Err(e) => { + println!("{:?}", e); + } + _ => {} } thread::sleep(Duration::from_millis(ctx.update_time as u64)); Ok(()) diff --git a/src/config.rs b/src/config.rs index f56f054..aded370 100644 --- a/src/config.rs +++ b/src/config.rs @@ -31,6 +31,8 @@ pub struct Config { pub disable_ip_hiding: bool, #[serde(default)] pub enable_auth: bool, + #[serde(default)] + pub enable_ssl: bool, } fn default_max_messages() -> usize { 200 } @@ -72,6 +74,7 @@ pub fn configure(path: PathBuf) -> Config { let enable_ip_viewing = ask_bool("Enable users IP viewing?", false); let disable_ip_hiding = ask_bool("Enable your IP viewing?", false); let enable_auth = ask_bool("Enable auth-mode?", false); + let enable_ssl = ask_bool("Enable SSL?", false); let config = Config { host, @@ -81,7 +84,8 @@ pub fn configure(path: PathBuf) -> Config { max_messages, enable_ip_viewing, disable_ip_hiding, - enable_auth + enable_auth, + enable_ssl }; let config_text = serde_yml::to_string(&config).expect("Config save error"); @@ -180,6 +184,10 @@ pub struct Args { /// Enable authentication #[arg(short='a', long)] pub enable_auth: bool, + + /// Enable SSL + #[arg(short='S', long)] + pub enable_ssl: bool, } pub struct Context { @@ -195,7 +203,8 @@ pub struct Context { pub max_messages: usize, pub enable_ip_viewing: bool, pub scroll: Arc, - pub auth: bool, + pub enable_auth: bool, + pub enable_ssl: bool, } impl Context { @@ -213,7 +222,8 @@ impl Context { max_messages: config.max_messages, enable_ip_viewing: args.enable_users_ip_viewing || config.enable_ip_viewing, scroll: Arc::new(AtomicUsize::new(0)), - auth: args.enable_auth || config.enable_auth + enable_auth: args.enable_auth || config.enable_auth, + enable_ssl: args.enable_ssl || config.enable_ssl } } } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 1c430cc..7424856 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use clap::Parser; use colored::Color; use config::{configure, get_config_path, load_config, Args, Context}; -use proto::{read_messages, send_message}; +use proto::{connect, read_messages, send_message}; use regex::Regex; use lazy_static::lazy_static; use chat::run_main_loop; @@ -47,11 +47,20 @@ fn main() { let ctx = Arc::new(Context::new(&config, &args)); if args.read_messages { - print!("{}", read_messages(&ctx.host, ctx.max_messages, 0).ok().flatten().expect("Error reading messages").0.join("\n")); + let mut stream = connect(&ctx.host, ctx.enable_ssl).expect("Error reading message"); + print!("{}", read_messages( + &mut stream, + ctx.max_messages, + 0, + !ctx.enable_ssl + ) + .ok().flatten() + .expect("Error reading messages").0.join("\n") + ); } if let Some(message) = &args.send_message { - send_message(&ctx.host, message).expect("Error sending message"); + send_message(&mut connect(&ctx.host, ctx.enable_ssl).expect("Error sending message"), message).expect("Error sending message"); } if args.send_message.is_some() || args.read_messages { diff --git a/src/proto.rs b/src/proto.rs index 3461824..9972a1e 100644 --- a/src/proto.rs +++ b/src/proto.rs @@ -1,18 +1,35 @@ -use std::{error::Error, io::{Read, Write}, net::TcpStream}; +use std::{error::Error, fmt::Debug, io::{Read, Write}, net::TcpStream}; +use native_tls::TlsConnector; +pub trait RacStream: Read + Write + Unpin + Send + Sync + Debug {} +impl RacStream for T {} -pub fn send_message(host: &str, message: &str) -> Result<(), Box> { - let mut stream = TcpStream::connect(host)?; +pub fn connect(host: &str, ssl: bool) -> Result, Box> { + Ok(if ssl { + let ip: String = host.split_once(":") + .map(|o| o.0) + .unwrap_or(host).to_string(); + + Box::new(TlsConnector::builder() + .danger_accept_invalid_certs(true) + .danger_accept_invalid_hostnames(true) + .build()? + .connect(&ip, connect(host, false)?)?) + } else { + Box::new(TcpStream::connect(host)?) + }) +} + +pub fn send_message(stream: &mut (impl Read + Write), message: &str) -> Result<(), Box> { stream.write_all(&[0x01])?; stream.write_all(message.as_bytes())?; Ok(()) } -pub fn send_message_auth(host: &str, message: &str) -> Result<(), Box> { - let Some((name, message)) = message.split_once("> ") else { return send_message(host, message) }; +pub fn send_message_auth(stream: &mut (impl Read + Write), message: &str) -> Result<(), Box> { + let Some((name, message)) = message.split_once("> ") else { return send_message(stream, message) }; - let mut stream = TcpStream::connect(host)?; stream.write_all(&[0x02])?; stream.write_all(name.as_bytes())?; stream.write_all(b"\n")?; @@ -23,16 +40,15 @@ pub fn send_message_auth(host: &str, message: &str) -> Result<(), Box let mut buf = vec![0; 1]; if let Ok(_) = stream.read_exact(&mut buf) { let name = format!("\x1f{name}"); - register_user(host, &name, &name)?; + register_user(stream, &name, &name)?; let message = format!("{name}> {message}"); - send_message_auth(host, &message) + send_message_auth(stream, &message) } else { Ok(()) } } -pub fn register_user(host: &str, name: &str, password: &str) -> Result<(), Box> { - let mut stream = TcpStream::connect(host)?; +pub fn register_user(stream: &mut (impl Read + Write), name: &str, password: &str) -> Result<(), Box> { stream.write_all(&[0x03])?; stream.write_all(name.as_bytes())?; stream.write_all(&[b'\n'])?; @@ -40,7 +56,7 @@ pub fn register_user(host: &str, name: &str, password: &str) -> Result<(), Box Result, Box> { +fn skip_null(stream: &mut impl Read) -> Result, Box> { loop { let mut buf = vec![0; 1]; stream.read_exact(&mut buf)?; @@ -50,23 +66,29 @@ fn skip_null(stream: &mut TcpStream) -> Result, Box> { } } -pub fn read_messages(host: &str, max_messages: usize, last_size: usize) -> Result, usize)>, Box> { - let mut stream = TcpStream::connect(host)?; - +pub fn read_messages(stream: &mut (impl Read + Write), max_messages: usize, last_size: usize, skip_null_: bool) -> Result, usize)>, Box> { stream.write_all(&[0x00])?; let packet_size = { - let mut data = skip_null(&mut stream)?; - - loop { - let mut buf = vec![0; 1]; - stream.read_exact(&mut buf)?; - let ch = buf[0]; - if ch == 0 { - break + let data = if skip_null_ { + let mut data = skip_null(stream)?; + + loop { + let mut buf = vec![0; 1]; + stream.read_exact(&mut buf)?; + let ch = buf[0]; + if ch == 0 { + break + } + data.push(ch); } - data.push(ch); - } + 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)) @@ -80,19 +102,27 @@ pub fn read_messages(host: &str, max_messages: usize, last_size: usize) -> Resul stream.write_all(&[0x01])?; let packet_data = { - let mut data = skip_null(&mut stream)?; - while data.len() < packet_size { - let mut buf = vec![0; packet_size - data.len()]; - let read_bytes = stream.read(&mut buf)?; - buf.truncate(read_bytes); - data.append(&mut buf); - } + let data = if skip_null_ { + let mut data = skip_null(stream)?; + while data.len() < packet_size { + let mut buf = vec![0; packet_size - data.len()]; + let read_bytes = stream.read(&mut buf)?; + buf.truncate(read_bytes); + data.append(&mut buf); + } + data + } else { + let mut data = vec![0; packet_size]; + stream.read_exact(&mut data)?; + data + }; + String::from_utf8_lossy(&data).to_string() }; let lines: Vec<&str> = packet_data.split("\n").collect(); let lines: Vec = lines.clone().into_iter() - .skip(lines.len() - max_messages) + .skip(if lines.len() >= max_messages { lines.len() - max_messages } else { 0 }) .map(|o| o.to_string()) .collect();