SSL support and secure

This commit is contained in:
MeexReay 2025-02-12 15:50:11 +03:00
parent 978fe2265e
commit a2be0e3235
6 changed files with 306 additions and 66 deletions

180
Cargo.lock generated
View File

@ -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"

View File

@ -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"

View File

@ -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<Context>, command: &str) -> Result<(), Box<dyn Error>> {
let args = args.split(" ").collect::<Vec<&str>>();
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::<u16>());
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<Context>, messages: Vec<String>, 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::<Vec<(String, usize)>>();
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<Context>, 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<Context>, message: String) -> Option<String> {
(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<Context>) -> Result<(), Box<dyn Error>> {
.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,7 +478,8 @@ fn poll_events(ctx: Arc<Context>) -> Result<(), Box<dyn Error>> {
}
pub fn recv_tick(ctx: Arc<Context>) -> Result<(), Box<dyn Error>> {
if let Ok(Some((messages, size))) = read_messages(&ctx.host, ctx.max_messages, ctx.messages.packet_size()) {
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<String> = if ctx.disable_formatting {
messages
} else {
@ -475,6 +488,11 @@ pub fn recv_tick(ctx: Arc<Context>) -> Result<(), Box<dyn Error>> {
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(())
}

View File

@ -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<AtomicUsize>,
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
}
}
}

View File

@ -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 {

View File

@ -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<T: Read + Write + Unpin + Send + Sync + Debug> RacStream for T {}
pub fn send_message(host: &str, message: &str) -> Result<(), Box<dyn Error>> {
let mut stream = TcpStream::connect(host)?;
pub fn connect(host: &str, ssl: bool) -> Result<Box<dyn RacStream>, Box<dyn Error>> {
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<dyn Error>> {
stream.write_all(&[0x01])?;
stream.write_all(message.as_bytes())?;
Ok(())
}
pub fn send_message_auth(host: &str, message: &str) -> Result<(), Box<dyn Error>> {
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<dyn Error>> {
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<dyn Error>
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<dyn Error>> {
let mut stream = TcpStream::connect(host)?;
pub fn register_user(stream: &mut (impl Read + Write), name: &str, password: &str) -> Result<(), Box<dyn Error>> {
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<d
Ok(())
}
fn skip_null(stream: &mut TcpStream) -> Result<Vec<u8>, Box<dyn Error>> {
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)?;
@ -50,13 +66,12 @@ fn skip_null(stream: &mut TcpStream) -> Result<Vec<u8>, Box<dyn Error>> {
}
}
pub fn read_messages(host: &str, max_messages: usize, last_size: usize) -> Result<Option<(Vec<String>, usize)>, Box<dyn Error>> {
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<Option<(Vec<String>, usize)>, Box<dyn Error>> {
stream.write_all(&[0x00])?;
let packet_size = {
let mut data = skip_null(&mut stream)?;
let data = if skip_null_ {
let mut data = skip_null(stream)?;
loop {
let mut buf = vec![0; 1];
@ -67,6 +82,13 @@ pub fn read_messages(host: &str, max_messages: usize, last_size: usize) -> Resul
}
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)?;
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<String> = 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();