From 0c848899a73407e629fd773a0df8aa691fa9e79d Mon Sep 17 00:00:00 2001 From: sad Date: Fri, 1 Mar 2024 04:25:44 -0700 Subject: [PATCH 01/19] complete rewrite --- .gitignore | 3 + Cargo.toml | 21 ++-- config.toml | 20 +--- proxies.txt | 78 --------------- src/main.rs | 199 ++++++++++++------------------------- src/modules/ai.rs | 89 ----------------- src/modules/invade copy.rs | 126 ----------------------- src/modules/invade.rs | 130 ------------------------ src/modules/kill.rs | 17 ---- src/modules/ping.rs | 17 ---- src/modules/test.rs | 0 11 files changed, 79 insertions(+), 621 deletions(-) create mode 100644 .gitignore delete mode 100644 proxies.txt delete mode 100644 src/modules/ai.rs delete mode 100644 src/modules/invade copy.rs delete mode 100644 src/modules/invade.rs delete mode 100644 src/modules/kill.rs delete mode 100644 src/modules/ping.rs delete mode 100644 src/modules/test.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fa04883 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +Cargo.lock +/target + diff --git a/Cargo.toml b/Cargo.toml index 30275af..8a7557c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,16 +6,13 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -openssl = "0.10.45" -async-openai = "0.6.1" -tokio = { version = "1.23.0", features = ["full"] } -rand = "0.8.4" -regex = "1.7.1" -toml = "0.7.2" -serde = "1.0.152" -tokio-openssl = "0.6.3" -colored = "2" +tokio = { version = "1.36.0", features = ["full"] } +tokio-openssl = "0.6.4" tokio-socks = "0.5.1" -socks = "0.3.4" -random_word = "0.3.0" -#leetspeak = "0.2.0" +openssl = "0.10.64" +rand = "0.8.5" +regex = "1.10.3" +toml = "0.8.10" +serde = { version = "1.0.197", features = ["derive"] } +colored = "2.1.0" +futures = "0.3.30" diff --git a/config.toml b/config.toml index 88ec755..966f19f 100644 --- a/config.toml +++ b/config.toml @@ -1,18 +1,6 @@ -nick = "g1r" -server = "ircd.chat" +server = "irc.supernets.org" port = 6697 -password = "" -channels = [ "#tcpdirect", "#macros", "#b0tsh0p" ] #add key access -admin_users = ["s4d", "s4d[m]", "g"] # add host identification -ignore_users = ["maple", "aibird", "professorOak", "van"] -openai = "sk-" -model = "text-davinci-003" -accents = "funny, completely insane, and hyperactive" -personalities = "GIR from Invader Zim" +use_ssl = true +nickname = "g1r" +channel = "#superbowl" -proxy_server = "127.0.0.1" -proxy_port = 9050 - -# INVADER SETTINGS -invaders = ["d1b", "z1m", "t4k", "m3mbr4n3", "g4z", "4l4n", "m1yuk1", "purpl3", "r3d", "5p0rk", "t3nn", "l4rb", "ch1nn", "d00ky", "3l", "fl0rb3", "g00ch", "gr4p4", "gr00t", "k00t", "k1m", "krunk", "l4dn4r", "n3n", "p3st0", "p00t"] -proxy_list = "./proxies.txt" diff --git a/proxies.txt b/proxies.txt deleted file mode 100644 index 290dda7..0000000 --- a/proxies.txt +++ /dev/null @@ -1,78 +0,0 @@ -184.185.2.12:4145 -174.138.33.62:59166 -206.220.175.2:4145 -96.126.113.216:59166 -205.240.77.164:4145 -174.75.211.222:4145 -72.210.252.137:4145 -192.111.129.145:16894 -68.71.254.6:4145 -159.203.13.82:59166 -107.170.81.141:59166 -159.65.220.89:59166 -192.241.143.216:59166 -149.56.247.67:59166 -157.245.223.201:59166 -203.57.114.228:59166 -192.111.135.18:18301 -192.252.216.81:4145 -68.183.90.210:59166 -123.171.245.162:1080 -72.195.34.58:4145 -208.111.40.144:59166 -98.162.25.29:31679 -192.252.214.20:15864 -95.213.228.10:59166 -142.93.77.151:59166 -72.195.114.169:4145 -192.111.135.17:18302 -75.119.157.170:59166 -75.127.13.195:59166 -167.71.10.234:59166 -51.255.219.244:59166 -51.178.82.49:59166 -184.178.172.14:4145 -68.71.247.130:4145 -192.111.137.37:18762 -176.58.125.165:59166 -205.186.162.190:59166 -47.243.95.228:10080 -142.54.228.193:4145 -168.196.160.61:59166 -138.68.124.120:59166 -174.138.63.144:59166 -74.119.147.209:4145 -68.183.20.254:59166 -67.205.181.126:59166 -139.144.31.132:59166 -159.89.49.172:59166 -43.226.26.120:59166 -23.253.253.26:59166 -206.189.157.253:59166 -184.170.248.5:4145 -159.203.78.174:59166 -46.101.37.189:59166 -184.170.249.65:4145 -192.111.137.34:18765 -167.71.190.131:59166 -174.64.199.79:4145 -184.178.172.25:15291 -45.79.46.53:59166 -40.87.45.45:59166 -199.58.184.97:4145 -198.8.94.170:4145 -192.252.208.67:14287 -184.170.245.148:4145 -69.164.224.53:59166 -68.71.249.153:48606 -143.198.229.170:59166 -165.227.153.96:59166 -192.252.220.89:4145 -138.197.185.192:59166 -151.80.45.47:59166 -69.194.181.6:7497 -170.210.156.33:59166 -209.94.62.123:59166 -198.8.94.174:39078 -184.178.172.23:4145 -72.221.232.155:4145 \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 857d908..f72e4cf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,151 +1,78 @@ -use std::io::prelude::*; -use std::net::TcpStream; -use std::io::Write; -use openssl::ssl::{SslMethod, SslConnector}; -use toml::Value; -use serde::Deserialize; use colored::*; - - -mod modules { - pub trait Command { - fn handle(&self, message: &str) -> Vec; - } - pub mod ping; - pub mod kill; - pub mod ai; - pub mod invade; - pub mod test; - //pub mod ai_invade; -} - -use modules::ai::Ai; // FIX THIS BS -use modules::ping::PingCommand; -use modules::invade::InvadeCommand; -//use modules::ai_invade::AiInvadeCommand; -use modules::kill::KillCommand; // ... -use crate::modules::Command; +use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; +use serde::Deserialize; +use std::fs; +use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader, BufWriter}; +use tokio::net::TcpStream; +use tokio_openssl::SslStream; +use std::pin::Pin; #[derive(Deserialize)] struct Config { server: String, port: u16, - nick: String, - password: String, - channels: Vec, - admin_users: Vec, - ignore_users: Vec, - + use_ssl: bool, + nickname: String, + channel: String, } -fn main() { - let rt = tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .unwrap(); +#[tokio::main] +async fn main() -> Result<(), Box<(dyn std::error::Error)>> { + println!("Loading Config..."); + let config_contents = fs::read_to_string("config.toml").expect("Error reading config.toml"); + let config: Config = toml::from_str(&config_contents).expect("Error parsing config.toml"); + println!("Config loaded!"); - // LOAD CONFIG - let config_str = std::fs::read_to_string("config.toml").unwrap(); - let config_value = config_str.parse::().unwrap(); - let config: Config = config_value.try_into().unwrap(); - // GIVE THE SERVER A SLOPPPY SPAM OF RETARDEDNESS - let stream = TcpStream::connect(format!("{}:{}", config.server, config.port)).unwrap(); - let connector = SslConnector::builder(SslMethod::tls()).unwrap().build(); - // DONT DO DRUGS YOU WILL END UP LIKE ME - let mut ssl_stream = connector.connect(&config.server, stream).unwrap(); - let nick_command = format!("NICK {}_\r\n", config.nick); //setup passwords - let user_command = format!("USER {} 0 * :{}\r\n", config.nick, config.nick); - ssl_stream.write_all(nick_command.as_bytes()).unwrap(); - ssl_stream.write_all(user_command.as_bytes()).unwrap(); - let identify_command = format!("PRIVMSG NickServ :IDENTIFY {} {}\r\n", config.nick, config.password); - ssl_stream.write(identify_command.as_bytes()).unwrap(); - let channels = config.channels.join(","); - let join_command = format!("JOIN {}\r\n", channels); - - let admin_users = config.admin_users; // ADMINS - let ignored_users = config.ignore_users; // IGNORED -// ... - ssl_stream.write_all(join_command.as_bytes()).unwrap(); + let addr = format!("{}:{}", config.server, config.port); + println!("Connecting to {}...", addr.green()); + let tcp_stream = TcpStream::connect(&addr).await.unwrap(); + println!("Connected to {}!", addr.green()); - let mut buf = [0; 512]; - loop { - match ssl_stream.read(&mut buf) { - Ok(0) => break, - Ok(n) => { - let received = String::from_utf8_lossy(&buf[0..n]); - let message = received.trim(); + if config.use_ssl { + println!("Establishing SSL connection..."); + let mut connector = SslConnector::builder(SslMethod::tls()).unwrap().build().configure().unwrap().into_ssl(&addr).unwrap(); + connector.set_verify(SslVerifyMode::NONE); + let mut ssl_stream = SslStream::new(connector, tcp_stream).unwrap(); - //debug chat - println!("{} {}","[%] DEBUG:".bold().green(), received.purple()); - - - // RESPOND TO PINGS - if message.starts_with("PING") { - println!("{} {}","[%] PONG:".bold().green(), config.nick.blue()); // DEBUG - ssl_stream.write_all("PONG ircd.chat\r\n".as_bytes()).unwrap(); - continue; // skip processing the PING message further - } - - // MODULES - let ping_command = PingCommand; - let kill_command = KillCommand; - let invade_command = InvadeCommand; - //let ai_invade_command = AiInvadeCommand; - - //let test_command = TestCommand; - let ai = Ai; - - // ADMIN MODULES - if message.starts_with(":") && message.contains(" :%") { - let parts: Vec<&str> = message.splitn(2, ' ').collect(); // Check if user is admin_user - let username = parts[0].trim_start_matches(':').split("!").next().unwrap(); - if !admin_users.contains(&username.to_string()) { - println!("{} {}","[!] UNAUTHORIZED:".bold().clear().on_red(), username.red().bold()); - continue; // ... - } - if message.contains(":%ping") { - for response in ping_command.handle(message) { - ssl_stream.write_all(response.as_bytes()).unwrap(); - } - } else if message.contains(":%kill") { - for response in kill_command.handle(message) { - ssl_stream.write_all(response.as_bytes()).unwrap(); - } - } else if message.contains(":%invade") { - for response in invade_command.handle(message) { - ssl_stream.write_all(response.as_bytes()).unwrap(); - } - } //else if message.contains(":%aiinvade") { - // for response in ai_invade_command.handle(message) { - // ssl_stream.write_all(response.as_bytes()).unwrap(); - // } - //} - } - - // Check if the message is user and respond via ai - else if message.starts_with(":") && message.contains("PRIVMSG ") && message.contains(&config.nick) { //modify for on mention - let channel = message.split("PRIVMSG ").nth(1).and_then(|s| s.splitn(2, ' ').next()).unwrap(); - if !channels.contains(&channel) { - continue; - } - // extract the username from the first part and check if ignored - let parts: Vec<&str> = message.splitn(2, ' ').collect(); // split the message into two parts at the first space - let username = parts[0].trim_start_matches(':').split("!").next().unwrap(); - if ignored_users.contains(&username.to_string()) { - println!("[!] IGNORED: {}", username.red()); - continue; - } - for response in ai.handle(message, ) { - ssl_stream.write_all(response.as_bytes()).unwrap(); - } - - } - - }, + // Perform the SSL handshake + match Pin::new(&mut ssl_stream).connect().await { + Ok(_) => println!("SSL connection established!"), Err(e) => { - println!("[!] ERROR: {}", e); - break; - }, + println!("Error establishing SSL connection: {:?}", e); + return Err(Box::new(e) as Box); + } + }; + + println!("Sending NICK and USER commands..."); + ssl_stream.write_all(format!("NICK {}\r\n", config.nickname).as_bytes()).await.unwrap(); + ssl_stream.write_all(format!("USER {} 0 * :{}\r\n", config.nickname, config.nickname).as_bytes()).await.unwrap(); + ssl_stream.write_all(format!("JOIN {}\r\n", config.channel).as_bytes()).await.unwrap(); + + + let (read_half, write_half) = tokio::io::split(ssl_stream); + + // split the stream and then transfer this for non-ssl + let mut reader = BufReader::new(read_half); + let mut writer = BufWriter::new(write_half); + let mut lines = reader.lines(); + + while let Some(result) = lines.next_line().await.unwrap() { + + let received = String::from_utf8_lossy(result.as_bytes()).trim().to_string(); + println!("{} {}","[%] DEBUG:".bold().green(), received.purple()); + + let message = received.trim(); + if message.starts_with("PING") { + println!("Sending PONG..."); + let response = message.replace("PING", "PONG"); + println!("{} {}","[%] PONG:".bold().green(), config.nickname.blue()); + writer.write_all(response.as_bytes()).await.unwrap(); + continue; + } } + } else { + println!("Non-SSL connection not implemented."); } + + Ok(()) } diff --git a/src/modules/ai.rs b/src/modules/ai.rs deleted file mode 100644 index ba7df92..0000000 --- a/src/modules/ai.rs +++ /dev/null @@ -1,89 +0,0 @@ -// Check if the message is user and respond via ai -use async_openai::{Client, types::{CreateCompletionRequestArgs}}; -use regex::Regex; -use crate::modules::Command; -use toml::Value; -use serde::Deserialize; -use colored::*; - -#[derive(Deserialize)] -struct Config { - nick: String, - openai: String, - model: String, - accents: String, - personalities: String, -} -pub struct Ai; - // setup a prompt and respnse log for training other bots -impl Command for Ai { - fn handle(&self, message: &str) -> Vec { - let mut responses = Vec::new(); - let config_str = std::fs::read_to_string("config.toml").unwrap(); - let config_value = config_str.parse::().unwrap(); - let config: Config = config_value.try_into().unwrap(); // respond to name with and without leet VVV - if message.starts_with(":") && message.contains("PRIVMSG ") && message.contains(&config.nick) { - let channel = message.split("PRIVMSG ").nth(1).and_then(|s| s.splitn(2, ' ').next()).unwrap(); // set the response to varible - let user_message = format!("The following is a chat log:\n{}\nRespond {} as you are chatting as {}: \n\n", - message.split(&format!("PRIVMSG {} :", channel.to_string())).nth(1).unwrap(), - config.accents, - config.personalities - ); - let parts: Vec<&str> = message.splitn(2, ' ').collect(); - let username = parts[0].trim_start_matches(':').split("!").next().unwrap(); - - let rt = tokio::runtime::Runtime::new().unwrap(); - let result = rt.block_on(ai(&user_message, &username, &channel)); - responses.extend(result); - } - - responses - } -} -async fn ai(user_message: &str, username: &str, channel: &str) -> Vec { - let config_str = std::fs::read_to_string("config.toml").unwrap(); - let config_value = config_str.parse::().unwrap(); - let config: Config = config_value.try_into().unwrap(); - let api_key = config.openai; // set this from config - - let client = Client::new().with_api_key(api_key); - println!("{} {} {}: {}", "[?]".on_green().bold(), "PROMPT:".green().bold(), username, user_message); - let chat_request = CreateCompletionRequestArgs::default() - .prompt(user_message) - .max_tokens(40_u16) - .model(config.model) - .build() - .unwrap(); - let chat_response = client - .completions() - .create(chat_request) - .await - .unwrap(); - println!("{} {} {}","[+]".on_green().bold(), "RESPONSE:".green().bold(), chat_response.choices.first().unwrap().text); - //modify regex for varible username ie G1R g1r GIR gir but as handle nick for bots - let response_text = &chat_response.choices.first().unwrap().text; - let regex = Regex::new(r#""|[gG][1iI][rR]:\s"#).unwrap(); // THIS IS FUCKING UP EVERYTHING - //let nick = &config.nick; - //let regex_str = format!( - // r#""|[{}{}{}]|\b[gG][1iI][rR]:\s*|\b[mM][eE]:?\s"#, - // nick.to_lowercase(), - // nick.to_uppercase(), - // nick.chars().map(|c| match c { /// regex magic nick removal in progress - // 'a' => '4', - // 'e' => '3', - // 'i' => '1', - // 'o' => '0', - // 's' => '5', - // _ => c, - // }).collect::(), - //); - //let regex = Regex::new(®ex_str).unwrap(); - let response_text = regex.replace_all(response_text, "").trim().to_string(); - let response_lines = response_text.split("\n").filter(|line| !line.trim().is_empty()); - let mut responses = Vec::new(); - for line in response_lines { - responses.push(format!("PRIVMSG {} :\x0313{}\x0f: {}\r\n", channel, username, line)); - } - - responses -} diff --git a/src/modules/invade copy.rs b/src/modules/invade copy.rs deleted file mode 100644 index 5b7134c..0000000 --- a/src/modules/invade copy.rs +++ /dev/null @@ -1,126 +0,0 @@ -use crate::modules::Command; - -use std::io::{BufRead, BufReader, Write}; -use std::net::TcpStream; -use openssl::ssl::{SslConnector, SslMethod}; -use serde::Deserialize; -use toml::{Value, to_string}; -use colored::*; - -// add better error handling -// add ai invasion -// add proxy support - -#[derive(Clone, Deserialize)] -struct Config { - //invaders: Vec, - server: String, - port: u16, - - -} - -pub struct InvadeCommand; - -impl Command for InvadeCommand { - fn handle(&self, message: &str) -> Vec { - let mut response = vec![]; - - if message.contains("PRIVMSG") && message.contains(":%invade") { - let parts: Vec<&str> = message.split_whitespace().collect(); - let num_invaders = parts[4].parse::().unwrap_or(1) as usize; - let channel = parts[2]; - let invadechannel = parts[5]; - let scream = if parts.len() > 6 { parts[6] } else { "" }; // read entire message - //message.split(&format!("PRIVMSG {} :", channel.to_string())).nth(1).unwrap(), - //let channel = message.split("PRIVMSG ").nth(1).and_then(|s| s.splitn(2, ' ').next()).unwrap(); - let config_str = std::fs::read_to_string("config.toml").unwrap(); - let config_value = config_str.parse::().unwrap(); - let config: Config = config_value.try_into().unwrap(); - - - for _invader in 1..=num_invaders {//&config.invaders[1..num_invaders] { //1..=20 { - let thread_channel = invadechannel.to_string(); - let config_clone = config.clone(); - let screaming = scream.to_string(); - let command_channel = channel.to_string(); - let thread_invader = random_word::gen(); // change to leetspeak on nick collision - - std::thread::spawn(move || { - let stream = TcpStream::connect((config_clone.server.as_str(), config_clone.port)).unwrap(); - let connector = SslConnector::builder(SslMethod::tls()).unwrap().build(); - let mut ssl_stream = connector.connect(config_clone.server.as_str(), stream).unwrap(); - let nick_command = format!("NICK {}\r\n", thread_invader); - let user_command = format!("USER {} 0 * :{}\r\n", thread_invader, thread_invader); - ssl_stream.write_all(nick_command.as_bytes()).unwrap(); - ssl_stream.write_all(user_command.as_bytes()).unwrap(); - let join_command = format!("JOIN {} \r\n", thread_channel); - let commander = format!("JOIN {} \r\n", command_channel); - ssl_stream.write_all(commander.as_bytes()).unwrap(); - ssl_stream.write_all(join_command.as_bytes()).unwrap(); - let msg = format!("PRIVMSG {} :{}\r\n", thread_channel, screaming); - ssl_stream.write_all(msg.as_bytes()).unwrap(); - - loop { - - - - let mut buf = [0; 512]; - match ssl_stream.ssl_read(&mut buf) { - Ok(0) => break, - Ok(n) => { - let received = String::from_utf8_lossy(&buf[0..n]); - let message = received.trim(); - - //debug chat - println!("{} {} {}","[%] DEBUG:".bold().green(), thread_invader.green(), received.blue()); - - if message.starts_with("PING") { - let response = message.replace("PING", "PONG"); - println!("{} {}","[%] PONG:".bold().green(), thread_invader.blue()); - ssl_stream.write_all(response.as_bytes()).unwrap(); - } - // turn to mods - // setup so these will only run from the server admin to avoid handle/host conflicts - let commandi = format!("PRIVMSG {} :%%",command_channel); // & check for admin and verify with server - - if message.contains(&commandi) && message.contains(":%%") { - if message.contains("PRIVMSG") && message.contains(":%%join") { // fix so commands get picked up faster - let parts: Vec<&str> = message.splitn(3, ":%%join ").collect(); - let invade_channel = parts[1]; - let response = format!("JOIN {} \r\n", invade_channel); - ssl_stream.write_all(response.as_bytes()).unwrap(); - } - if message.contains("PRIVMSG") && message.contains(":%%leave") { - let parts: Vec<&str> = message.splitn(3, ":%%leave ").collect(); - let invade_channel = parts[1]; - let response = format!("PART {} \r\n", invade_channel); - ssl_stream.write_all(response.as_bytes()).unwrap(); - } - if message.contains("PRIVMSG") && message.contains(":%%scream") { - let parts: Vec<&str> = message.splitn(3, ":%%scream ").collect(); - let invade_channel = parts[1]; - if parts.len() == 2 { - let scream = parts[1]; - let response = format!("PRIVMSG {} :{}\r\n", invade_channel, scream); - ssl_stream.write_all(response.as_bytes()).unwrap(); - } - } - } - // ...1 - } - Err(e) => { - eprintln!("{} {}","[!] ERROR FROM SERVER:".on_red(), e); - break; - } - } - } - }); - } - - response.push(format!("PRIVMSG {} :\x0304,01[!] INVADING {} WITH {} INVADERS...\x0f\r\n", channel, invadechannel, num_invaders)); - } - - response - } -} \ No newline at end of file diff --git a/src/modules/invade.rs b/src/modules/invade.rs deleted file mode 100644 index ba612f8..0000000 --- a/src/modules/invade.rs +++ /dev/null @@ -1,130 +0,0 @@ -use crate::modules::Command; - -use std::io::{BufRead, BufReader, Write}; -use std::net::TcpStream; -use openssl::ssl::{SslConnector, SslMethod}; -use serde::Deserialize; -use toml::{Value, to_string}; -use colored::*; -use socks::*; -// add better error handling -// add ai invasion -// add proxy support - -#[derive(Clone, Deserialize)] -struct Config { - //invaders: Vec, - server: String, - port: u16, - - proxy_server: String, - proxy_port: u16, - -} - -pub struct InvadeCommand; - -impl Command for InvadeCommand { - fn handle(&self, message: &str) -> Vec { - let mut response = vec![]; - - if message.contains("PRIVMSG") && message.contains(":%invade") { - let parts: Vec<&str> = message.split_whitespace().collect(); - let num_invaders = parts[4].parse::().unwrap_or(1) as usize; - let channel = parts[2]; - let invadechannel = parts[5]; - let scream = if parts.len() > 6 { parts[6] } else { "" }; // read entire message - //message.split(&format!("PRIVMSG {} :", channel.to_string())).nth(1).unwrap(), - //let channel = message.split("PRIVMSG ").nth(1).and_then(|s| s.splitn(2, ' ').next()).unwrap(); - let config_str = std::fs::read_to_string("config.toml").unwrap(); - let config_value = config_str.parse::().unwrap(); - let config: Config = config_value.try_into().unwrap(); - - - for _invader in 1..=num_invaders {//&config.invaders[1..num_invaders] { //1..=20 { - let thread_channel = invadechannel.to_string(); - let config_clone = config.clone(); - let screaming = scream.to_string(); - let command_channel = channel.to_string(); - let thread_invader = random_word::gen(); // change to leetspeak on nick collision - - std::thread::spawn(move || { - - let stream = TcpStream::connect((config_clone.server.as_str(), config_clone.port)).unwrap(); - let connector = SslConnector::builder(SslMethod::tls()).unwrap().build(); - let mut ssl_stream = connector.connect(config_clone.server.as_str(), stream).unwrap(); - - let nick_command = format!("NICK {}\r\n", thread_invader); - let user_command = format!("USER {} 0 * :{}\r\n", thread_invader, thread_invader); - ssl_stream.write_all(nick_command.as_bytes()).unwrap(); - ssl_stream.write_all(user_command.as_bytes()).unwrap(); - let join_command = format!("JOIN {} \r\n", thread_channel); - let commander = format!("JOIN {} \r\n", command_channel); - ssl_stream.write_all(commander.as_bytes()).unwrap(); - ssl_stream.write_all(join_command.as_bytes()).unwrap(); - let msg = format!("PRIVMSG {} :{}\r\n", thread_channel, screaming); - ssl_stream.write_all(msg.as_bytes()).unwrap(); - - loop { - - - - let mut buf = [0; 512]; - match ssl_stream.ssl_read(&mut buf) { - Ok(0) => break, - Ok(n) => { - let received = String::from_utf8_lossy(&buf[0..n]); - let message = received.trim(); - - //debug chat - println!("{} {} {}","[%] DEBUG:".bold().green(), thread_invader.green(), received.blue()); - - if message.starts_with("PING") { - let response = message.replace("PING", "PONG"); - println!("{} {}","[%] PONG:".bold().green(), thread_invader.blue()); - ssl_stream.write_all(response.as_bytes()).unwrap(); - } - // turn to mods - // setup so these will only run from the server admin to avoid handle/host conflicts - let commandi = format!("PRIVMSG {} :%%",command_channel); // & check for admin and verify with server - - if message.contains(&commandi) && message.contains(":%%") { - if message.contains("PRIVMSG") && message.contains(":%%join") { // fix so commands get picked up faster - let parts: Vec<&str> = message.splitn(3, ":%%join ").collect(); - let invade_channel = parts[1]; - let response = format!("JOIN {} \r\n", invade_channel); - ssl_stream.write_all(response.as_bytes()).unwrap(); - } - if message.contains("PRIVMSG") && message.contains(":%%leave") { - let parts: Vec<&str> = message.splitn(3, ":%%leave ").collect(); - let invade_channel = parts[1]; - let response = format!("PART {} \r\n", invade_channel); - ssl_stream.write_all(response.as_bytes()).unwrap(); - } - if message.contains("PRIVMSG") && message.contains(":%%scream") { - let parts: Vec<&str> = message.splitn(3, ":%%scream ").collect(); - let invade_channel = parts[1]; - if parts.len() == 2 { - let scream = parts[1]; - let response = format!("PRIVMSG {} :{}\r\n", invade_channel, scream); - ssl_stream.write_all(response.as_bytes()).unwrap(); - } - } - } - // ...1 - } - Err(e) => { - eprintln!("{} {}","[!] ERROR FROM SERVER:".on_red(), e); - break; - } - } - } - }); - } - - response.push(format!("PRIVMSG {} :\x0304,01[!] INVADING {} WITH {} INVADERS...\x0f\r\n", channel, invadechannel, num_invaders)); - } - - response - } -} \ No newline at end of file diff --git a/src/modules/kill.rs b/src/modules/kill.rs deleted file mode 100644 index 2696506..0000000 --- a/src/modules/kill.rs +++ /dev/null @@ -1,17 +0,0 @@ - -use crate::modules::Command; - -pub struct KillCommand; -impl Command for KillCommand { - fn handle(&self, message: &str) -> Vec { - let mut response = vec![]; - - if message.contains("PRIVMSG") && message.contains(":%kill") { - let channel = message.split("PRIVMSG ").nth(1).and_then(|s| s.splitn(2, ' ').next()).unwrap(); - response.push(format!("PRIVMSG {} :SELF DESTRUCTING...\r\n", channel)); - println!("[!] KILLING!"); - std::process::exit(0); - } - response - } -} diff --git a/src/modules/ping.rs b/src/modules/ping.rs deleted file mode 100644 index 80ace04..0000000 --- a/src/modules/ping.rs +++ /dev/null @@ -1,17 +0,0 @@ - -use std::time::{Instant}; -use crate::modules::Command; -pub struct PingCommand; -impl Command for PingCommand { - fn handle(&self, message: &str) -> Vec { - let mut response = vec![]; - - if message.contains("PRIVMSG") && message.contains(":%ping") { - let channel = message.split("PRIVMSG ").nth(1).and_then(|s| s.splitn(2, ' ').next()).unwrap(); - let start = Instant::now(); - let elapsed = start.elapsed(); - response.push(format!("PRIVMSG {} :PONG: {:?}\r\n", channel, elapsed)); - } - response - } -} diff --git a/src/modules/test.rs b/src/modules/test.rs deleted file mode 100644 index e69de29..0000000 -- 2.39.5 From 087cef1ffe97185daf3dd4ad8627eafefee394b3 Mon Sep 17 00:00:00 2001 From: sad Date: Sat, 2 Mar 2024 03:01:30 -0700 Subject: [PATCH 02/19] add multithreading + rewrote async --- Cargo.toml | 6 +-- src/main.rs | 105 ++++++++++++++++++++++++++++++---------------------- 2 files changed, 63 insertions(+), 48 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8a7557c..725dbf5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,11 +7,11 @@ edition = "2021" [dependencies] tokio = { version = "1.36.0", features = ["full"] } -tokio-openssl = "0.6.4" tokio-socks = "0.5.1" -openssl = "0.10.64" +tokio-rustls = "0.25.0" +tokio-native-tls = "0.3.1" +native-tls = "0.2.11" rand = "0.8.5" -regex = "1.10.3" toml = "0.8.10" serde = { version = "1.0.197", features = ["derive"] } colored = "2.1.0" diff --git a/src/main.rs b/src/main.rs index f72e4cf..55c5f18 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,11 @@ -use colored::*; -use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; +use tokio::io::{split, AsyncReadExt, AsyncWriteExt}; +use tokio::net::TcpStream; +use tokio_native_tls::native_tls::TlsConnector as NTlsConnector; +use tokio_native_tls::TlsConnector; +use tokio::sync::mpsc; use serde::Deserialize; use std::fs; -use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader, BufWriter}; -use tokio::net::TcpStream; -use tokio_openssl::SslStream; -use std::pin::Pin; +use colored::*; #[derive(Deserialize)] struct Config { @@ -16,8 +16,8 @@ struct Config { channel: String, } -#[tokio::main] -async fn main() -> Result<(), Box<(dyn std::error::Error)>> { +#[tokio::main(flavor = "multi_thread", worker_threads = 12)] +async fn main() -> Result<(), Box> { println!("Loading Config..."); let config_contents = fs::read_to_string("config.toml").expect("Error reading config.toml"); let config: Config = toml::from_str(&config_contents).expect("Error parsing config.toml"); @@ -25,51 +25,66 @@ async fn main() -> Result<(), Box<(dyn std::error::Error)>> { let addr = format!("{}:{}", config.server, config.port); println!("Connecting to {}...", addr.green()); - let tcp_stream = TcpStream::connect(&addr).await.unwrap(); + let tcp_stream = TcpStream::connect(&addr).await?; println!("Connected to {}!", addr.green()); if config.use_ssl { - println!("Establishing SSL connection..."); - let mut connector = SslConnector::builder(SslMethod::tls()).unwrap().build().configure().unwrap().into_ssl(&addr).unwrap(); - connector.set_verify(SslVerifyMode::NONE); - let mut ssl_stream = SslStream::new(connector, tcp_stream).unwrap(); + println!("Establishing TLS connection..."); + let mut tls_builder = NTlsConnector::builder(); + tls_builder.danger_accept_invalid_certs(true); + let tls_connector = TlsConnector::from(tls_builder.build()?); + let domain = &config.server; + let tls_stream = tls_connector.connect(domain, tcp_stream).await?; + println!("TLS connection established!"); - // Perform the SSL handshake - match Pin::new(&mut ssl_stream).connect().await { - Ok(_) => println!("SSL connection established!"), - Err(e) => { - println!("Error establishing SSL connection: {:?}", e); - return Err(Box::new(e) as Box); + let (reader, writer) = split(tls_stream); + let (tx, mut rx) = mpsc::channel(1000); + // Spawn a task to handle reading + let read_task = tokio::spawn(async move { + let mut reader = reader; + let mut buf = vec![0; 4096]; + loop { + let n = match reader.read(&mut buf).await { + Ok(0) => return, // connection was closed + Ok(n) => n, + Err(e) => { + eprintln!("Error reading from socket: {:?}", e); + return; + } + }; + + let msg = String::from_utf8_lossy(&buf[..n]).to_string(); + if tx.send(msg).await.is_err() { + eprintln!("Error sending message to the channel"); + return; + } } - }; + }); - println!("Sending NICK and USER commands..."); - ssl_stream.write_all(format!("NICK {}\r\n", config.nickname).as_bytes()).await.unwrap(); - ssl_stream.write_all(format!("USER {} 0 * :{}\r\n", config.nickname, config.nickname).as_bytes()).await.unwrap(); - ssl_stream.write_all(format!("JOIN {}\r\n", config.channel).as_bytes()).await.unwrap(); + let write_task = tokio::spawn(async move { + let mut writer = writer; + writer.write_all(format!("NICK {}\r\n", config.nickname).as_bytes()).await.unwrap(); + writer.write_all(format!("USER {} 0 * :{}\r\n", config.nickname, config.nickname).as_bytes()).await.unwrap(); + writer.write_all(format!("JOIN {}\r\n", config.channel).as_bytes()).await.unwrap(); + writer.flush().await.unwrap(); - - let (read_half, write_half) = tokio::io::split(ssl_stream); - - // split the stream and then transfer this for non-ssl - let mut reader = BufReader::new(read_half); - let mut writer = BufWriter::new(write_half); - let mut lines = reader.lines(); - - while let Some(result) = lines.next_line().await.unwrap() { - - let received = String::from_utf8_lossy(result.as_bytes()).trim().to_string(); - println!("{} {}","[%] DEBUG:".bold().green(), received.purple()); - - let message = received.trim(); - if message.starts_with("PING") { - println!("Sending PONG..."); - let response = message.replace("PING", "PONG"); - println!("{} {}","[%] PONG:".bold().green(), config.nickname.blue()); - writer.write_all(response.as_bytes()).await.unwrap(); - continue; + while let Some(msg) = rx.recv().await { + // handle messages better + println!("{} {}", "[%] DEBUG:".bold().green(), msg.purple()); + if msg.starts_with("PING") { + writer.write_all(format!("PONG {}\r\n", &msg[5..]).as_bytes()).await.unwrap(); + } + // super dirty auto-rejoin on kick REWRITE THIS + if let Some(pos) = msg.find(" KICK ") { + let parts: Vec<&str> = msg[pos..].split_whitespace().collect(); + if parts.len() > 3 && parts[2] == config.nickname { + writer.write_all(format!("JOIN {}\r\n", config.channel).as_bytes()).await.unwrap(); + } + } } - } + }); + + let _ = tokio::try_join!(read_task, write_task); } else { println!("Non-SSL connection not implemented."); } -- 2.39.5 From e83663ffe522b88ad8cf09381138a148818a7084 Mon Sep 17 00:00:00 2001 From: sad Date: Sat, 2 Mar 2024 04:54:16 -0700 Subject: [PATCH 03/19] base caps+sasl implementation --- Cargo.toml | 1 + config.toml | 6 ++++-- src/main.rs | 42 ++++++++++++++++++++++++++++++++----- src/mods/sasl.rs | 54 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 96 insertions(+), 7 deletions(-) create mode 100644 src/mods/sasl.rs diff --git a/Cargo.toml b/Cargo.toml index 725dbf5..46a9916 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ tokio-native-tls = "0.3.1" native-tls = "0.2.11" rand = "0.8.5" toml = "0.8.10" +base64 = "0.22.0" serde = { version = "1.0.197", features = ["derive"] } colored = "2.1.0" futures = "0.3.30" diff --git a/config.toml b/config.toml index 966f19f..1bc452f 100644 --- a/config.toml +++ b/config.toml @@ -2,5 +2,7 @@ server = "irc.supernets.org" port = 6697 use_ssl = true nickname = "g1r" -channel = "#superbowl" - +channel = "#dev" +sasl_username = "g1r" +sasl_password = "fuckyou.lol" +capabilities = ["sasl"] diff --git a/src/main.rs b/src/main.rs index 55c5f18..a3aae51 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,8 +14,16 @@ struct Config { use_ssl: bool, nickname: String, channel: String, + sasl_username: Option, + sasl_password: Option, + capabilities: Option> } +mod mods { + pub mod sasl; +} +use mods::sasl::{start_sasl_auth, handle_sasl_messages}; + #[tokio::main(flavor = "multi_thread", worker_threads = 12)] async fn main() -> Result<(), Box> { println!("Loading Config..."); @@ -39,13 +47,12 @@ async fn main() -> Result<(), Box> { let (reader, writer) = split(tls_stream); let (tx, mut rx) = mpsc::channel(1000); - // Spawn a task to handle reading let read_task = tokio::spawn(async move { let mut reader = reader; let mut buf = vec![0; 4096]; loop { let n = match reader.read(&mut buf).await { - Ok(0) => return, // connection was closed + Ok(0) => return, // connection has been killed x.x Ok(n) => n, Err(e) => { eprintln!("Error reading from socket: {:?}", e); @@ -58,19 +65,44 @@ async fn main() -> Result<(), Box> { eprintln!("Error sending message to the channel"); return; } + if msg.contains("AUTHENTICATE") || msg.contains("903") { + handle_sasl_messages(&mut writer, &msg, &config.sasl_username.unwrap(), &config.sasl_password.unwrap()).await.unwrap(); + } } + //let msg = String::from_utf8_lossy(&buf[..n]).to_string(); + //if msg.contains("AUTHENTICATE") || msg.contains("903") { + // handle_sasl_messages(&mut writer, &msg, &sasl_username.unwrap(), &sasl_password.unwrap()).await; + //} }); + // capabilities + let mut capabilities = config.capabilities.clone().unwrap_or_else(Vec::new); + //if config.capabilities.is_some() && config.sasl_password.is_some() && config.sasl_username.is_some() { + if config.sasl_username.is_some() && config.sasl_password.is_some() && !capabilities.contains(&"sasl".to_string()) { + capabilities.push("sasl".to_string()); + } let write_task = tokio::spawn(async move { let mut writer = writer; - writer.write_all(format!("NICK {}\r\n", config.nickname).as_bytes()).await.unwrap(); - writer.write_all(format!("USER {} 0 * :{}\r\n", config.nickname, config.nickname).as_bytes()).await.unwrap(); - writer.write_all(format!("JOIN {}\r\n", config.channel).as_bytes()).await.unwrap(); + + // capabilities + //let capabilities = config.capabilities.clone().unwrap_or_else(Vec::new); + if !capabilities.is_empty() { + let cap_req = format!("CAP REQ :{}\r\n", capabilities.join(" ")); + writer.write_all(cap_req.as_bytes()).await.unwrap(); + // handle that CAP ACK yo + } // proceeding with sasl if creds are availble + if let (Some(sasl_username), Some(sasl_password)) = (&config.sasl_username, &config.sasl_password) { + start_sasl_auth(&mut writer, "PLAIN", &config.nickname, &capabilities).await.unwrap(); + } else { + writer.write_all(format!("NICK {}\r\n", config.nickname).as_bytes()).await.unwrap(); + writer.write_all(format!("USER {} 0 * :{}\r\n", config.nickname, config.nickname).as_bytes()).await.unwrap(); + } writer.flush().await.unwrap(); while let Some(msg) = rx.recv().await { // handle messages better println!("{} {}", "[%] DEBUG:".bold().green(), msg.purple()); + if msg.starts_with("PING") { writer.write_all(format!("PONG {}\r\n", &msg[5..]).as_bytes()).await.unwrap(); } diff --git a/src/mods/sasl.rs b/src/mods/sasl.rs new file mode 100644 index 0000000..020c1ba --- /dev/null +++ b/src/mods/sasl.rs @@ -0,0 +1,54 @@ +// mods/sasl.rs +use base64::Engine; +use tokio::io::AsyncWriteExt; +/// Sends the initial commands to negotiate capabilities and start SASL authentication. +pub async fn start_sasl_auth( +//pub async fn start_sasl_auth(...) -> Result<(), Box> { + writer: &mut W, + mechanism: &str, + nickname: &str, + capabilities: &[String], // Add a parameter for capabilities +) -> Result<(), Box> { + // Request a list of capabilities from the server + writer.write_all(b"CAP LS 302\r\n").await?; + + // Send NICK and USER commands + let nick_cmd = format!("NICK {}\r\n", nickname); + writer.write_all(nick_cmd.as_bytes()).await?; + let user_cmd = format!("USER {} 0 * :{}\r\n", nickname, nickname); + writer.write_all(user_cmd.as_bytes()).await?; + + // Request specific capabilities, including 'sasl' for SASL authentication + if !capabilities.is_empty() { + let cap_req_cmd = format!("CAP REQ :{}\r\n", capabilities.join(" ")); + writer.write_all(cap_req_cmd.as_bytes()).await?; + } else { + // If no specific capabilities are requested, directly request 'sasl' + writer.write_all(b"CAP REQ :sasl\r\n").await?; + } + + writer.flush().await?; + Ok(()) +} + +/// Continues the SASL authentication process based on the server's responses. +//pub async fn handle_sasl_messages(...) -> Result<(), Box> { +pub async fn handle_sasl_messages( + writer: &mut W, + message: &str, + username: &str, + password: &str, +) -> Result<(), Box> { + if message.contains("CAP * ACK :sasl") { + writer.write_all(b"AUTHENTICATE PLAIN\r\n").await?; + } else if message.starts_with("AUTHENTICATE +") { + let auth_string = format!("\0{}\0{}", username, password); + let encoded = base64::engine::general_purpose::STANDARD.encode(auth_string); + writer.write_all(format!("AUTHENTICATE {}\r\n", encoded).as_bytes()).await?; + } else if message.contains("903 * :SASL authentication successful") { + writer.write_all(b"CAP END\r\n").await?; + } + writer.flush().await?; + Ok(()) +} + -- 2.39.5 From 500ce3b59e6cbf32b68ce3af2fdd4ddf45d03a48 Mon Sep 17 00:00:00 2001 From: sad Date: Sat, 2 Mar 2024 05:23:47 -0700 Subject: [PATCH 04/19] refactor+cleanup --- src/main.rs | 176 +++++++++++++++++++++++++++------------------------- 1 file changed, 92 insertions(+), 84 deletions(-) diff --git a/src/main.rs b/src/main.rs index a3aae51..b76ef76 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,99 +27,107 @@ use mods::sasl::{start_sasl_auth, handle_sasl_messages}; #[tokio::main(flavor = "multi_thread", worker_threads = 12)] async fn main() -> Result<(), Box> { println!("Loading Config..."); - let config_contents = fs::read_to_string("config.toml").expect("Error reading config.toml"); - let config: Config = toml::from_str(&config_contents).expect("Error parsing config.toml"); + let config = loaded_config().expect("Error parsing config.toml"); println!("Config loaded!"); - let addr = format!("{}:{}", config.server, config.port); - println!("Connecting to {}...", addr.green()); - let tcp_stream = TcpStream::connect(&addr).await?; - println!("Connected to {}!", addr.green()); - if config.use_ssl { + let tcp_stream = TcpStream::connect(format!("{}:{}", config.server, config.port)).await?; + println!("Connected to {}!", format!("{}:{}", config.server, config.port).green()); + println!("Establishing TLS connection..."); - let mut tls_builder = NTlsConnector::builder(); - tls_builder.danger_accept_invalid_certs(true); - let tls_connector = TlsConnector::from(tls_builder.build()?); - let domain = &config.server; - let tls_stream = tls_connector.connect(domain, tcp_stream).await?; + let tls_stream = tls_exec (&config, tcp_stream).await?; println!("TLS connection established!"); - let (reader, writer) = split(tls_stream); - let (tx, mut rx) = mpsc::channel(1000); - let read_task = tokio::spawn(async move { - let mut reader = reader; - let mut buf = vec![0; 4096]; - loop { - let n = match reader.read(&mut buf).await { - Ok(0) => return, // connection has been killed x.x - Ok(n) => n, - Err(e) => { - eprintln!("Error reading from socket: {:?}", e); - return; - } - }; - - let msg = String::from_utf8_lossy(&buf[..n]).to_string(); - if tx.send(msg).await.is_err() { - eprintln!("Error sending message to the channel"); - return; - } - if msg.contains("AUTHENTICATE") || msg.contains("903") { - handle_sasl_messages(&mut writer, &msg, &config.sasl_username.unwrap(), &config.sasl_password.unwrap()).await.unwrap(); - } - } - //let msg = String::from_utf8_lossy(&buf[..n]).to_string(); - //if msg.contains("AUTHENTICATE") || msg.contains("903") { - // handle_sasl_messages(&mut writer, &msg, &sasl_username.unwrap(), &sasl_password.unwrap()).await; - //} - }); - // capabilities - let mut capabilities = config.capabilities.clone().unwrap_or_else(Vec::new); - //if config.capabilities.is_some() && config.sasl_password.is_some() && config.sasl_username.is_some() { - if config.sasl_username.is_some() && config.sasl_password.is_some() && !capabilities.contains(&"sasl".to_string()) { - capabilities.push("sasl".to_string()); - } - - let write_task = tokio::spawn(async move { - let mut writer = writer; - - // capabilities - //let capabilities = config.capabilities.clone().unwrap_or_else(Vec::new); - if !capabilities.is_empty() { - let cap_req = format!("CAP REQ :{}\r\n", capabilities.join(" ")); - writer.write_all(cap_req.as_bytes()).await.unwrap(); - // handle that CAP ACK yo - } // proceeding with sasl if creds are availble - if let (Some(sasl_username), Some(sasl_password)) = (&config.sasl_username, &config.sasl_password) { - start_sasl_auth(&mut writer, "PLAIN", &config.nickname, &capabilities).await.unwrap(); - } else { - writer.write_all(format!("NICK {}\r\n", config.nickname).as_bytes()).await.unwrap(); - writer.write_all(format!("USER {} 0 * :{}\r\n", config.nickname, config.nickname).as_bytes()).await.unwrap(); - } - writer.flush().await.unwrap(); - - while let Some(msg) = rx.recv().await { - // handle messages better - println!("{} {}", "[%] DEBUG:".bold().green(), msg.purple()); - - if msg.starts_with("PING") { - writer.write_all(format!("PONG {}\r\n", &msg[5..]).as_bytes()).await.unwrap(); - } - // super dirty auto-rejoin on kick REWRITE THIS - if let Some(pos) = msg.find(" KICK ") { - let parts: Vec<&str> = msg[pos..].split_whitespace().collect(); - if parts.len() > 3 && parts[2] == config.nickname { - writer.write_all(format!("JOIN {}\r\n", config.channel).as_bytes()).await.unwrap(); - } - } - } - }); - - let _ = tokio::try_join!(read_task, write_task); + handler(tls_stream, &config).await?; } else { println!("Non-SSL connection not implemented."); } Ok(()) } + +fn loaded_config() -> Result> { + let config_contents = fs::read_to_string("config.toml")?; + //let config_contents = fs::read_to_string("config.toml").expect("Error reading config.toml"); + let config: Config = toml::from_str(&config_contents)?; + //let config: Config = toml::from_str(&config_contents).expect("Error parsing config.toml"); + Ok(config) +} + +//async fn tls_exec(config: &Config, tcp_stream: TcpStream) -> Result, Box> { +// let mut tls_builder = NTlsConnector::builder(); +// tls_builder.danger_accept_invalid_certs(true); +// let tls_connector = TlsConnector::from(tls_builder.build()?); +// let domain = &config.server; +// let tls_stream = tls_connector.connect(domain, tcp_stream).await?; +// println!("TLS connection established!"); +// Ok(tls_stream) +//} + +async fn tls_exec(config: &Config, tcp_stream: TcpStream) -> Result, Box> { + let tls_builder = NTlsConnector::builder().danger_accept_invalid_certs(true).build()?; + let tls_connector = TlsConnector::from(tls_builder); + Ok(tls_connector.connect(&config.server, tcp_stream).await?) +} + +async fn handler(tls_stream: tokio_native_tls::TlsStream, config: &Config) -> Result<(), Box> { +//async fn handler(mut tls_stream: tokio_native_tls::TlsStream, config: &Config) -> Result<(), Box> { + let (mut reader, mut writer) = split(tls_stream); + let (tx, mut rx) = mpsc::channel(1000); + + let read_task = tokio::spawn(async move { + let mut buf = vec![0; 4096]; + while let Ok(n) = reader.read(&mut buf).await { + if n == 0 { break; } // connection killed x.x + let msg = String::from_utf8_lossy(&buf[..n]).to_string(); + if tx.send(msg).await.is_err() { break; } // channel killed x.x + } + }); + + //let read_task = tokio::spawn(async move { + // let mut buf = vec![0; 4096]; + // loop { + // let n = match reader.read(&mut buf).await { + // Ok(0) => return, // connection killed x.x + // Ok(n) => n, + // Err(e) => { + // eprintln!("Error reading from socket: {:?}", e); + // return; + // }, + // }; + // + // let msg = String::from_utf8_lossy(&buf[..n]).to_string(); + // if tx.send(msg).await.is_err() { + // eprintln!("Error sending message to the channel"); + // return; + // } + // } + //}); + // + let write_task = tokio::spawn(async move { + while let Some(msg) = rx.recv().await { + // new commands here + if msg.starts_with("PING") { + writer.write_all(format!("PONG {}\r\n", &msg[5..]).as_bytes()).await.unwrap(); + } + } + }); + + //let write_task = tokio::spawn(async move { + // while let Some(msg) = rx.recv().await { + // if msg.starts_with("PING") { + // writer.write_all(format!("PONG {}\r\n", &msg[5..]).as_bytes()).await.unwrap(); + // } + // if let Some(username) = &config.sasl_username { + // if let Some(password) = &config.sasl_password { + // handle_sasl_messages(&mut writer, &msg, username, password).await.unwrap(); + // } + // } + // } + //}); + + let _ = tokio::try_join!(read_task, write_task); + + Ok(()) +} + -- 2.39.5 From f02b9a237b4a09db430f166a9969c8ae683c9b65 Mon Sep 17 00:00:00 2001 From: sad Date: Sat, 2 Mar 2024 10:16:29 -0700 Subject: [PATCH 05/19] verifiy connections --- src/main.rs | 164 ++++++++++++++++++++++++++++------------------- src/mods/sasl.rs | 24 +++---- 2 files changed, 107 insertions(+), 81 deletions(-) diff --git a/src/main.rs b/src/main.rs index b76ef76..190d4b3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,9 @@ use tokio_native_tls::native_tls::TlsConnector as NTlsConnector; use tokio_native_tls::TlsConnector; use tokio::sync::mpsc; use serde::Deserialize; +use tokio_rustls::rustls::Writer; use std::fs; +use std::future::IntoFuture; use colored::*; #[derive(Deserialize)] @@ -16,7 +18,7 @@ struct Config { channel: String, sasl_username: Option, sasl_password: Option, - capabilities: Option> + capabilities: Option>, } mod mods { @@ -35,99 +37,129 @@ async fn main() -> Result<(), Box> { println!("Connected to {}!", format!("{}:{}", config.server, config.port).green()); println!("Establishing TLS connection..."); - let tls_stream = tls_exec (&config, tcp_stream).await?; + let mut tls_stream = tls_exec (&config, tcp_stream).await?; println!("TLS connection established!"); + tls_stream.flush().await?; - handler(tls_stream, &config).await?; + handler(tls_stream, config).await?; } else { println!("Non-SSL connection not implemented."); } Ok(()) } - +/// Load the config file fn loaded_config() -> Result> { let config_contents = fs::read_to_string("config.toml")?; - //let config_contents = fs::read_to_string("config.toml").expect("Error reading config.toml"); let config: Config = toml::from_str(&config_contents)?; - //let config: Config = toml::from_str(&config_contents).expect("Error parsing config.toml"); Ok(config) } -//async fn tls_exec(config: &Config, tcp_stream: TcpStream) -> Result, Box> { -// let mut tls_builder = NTlsConnector::builder(); -// tls_builder.danger_accept_invalid_certs(true); -// let tls_connector = TlsConnector::from(tls_builder.build()?); -// let domain = &config.server; -// let tls_stream = tls_connector.connect(domain, tcp_stream).await?; -// println!("TLS connection established!"); -// Ok(tls_stream) -//} - +/// Establish a TLS connection to the server async fn tls_exec(config: &Config, tcp_stream: TcpStream) -> Result, Box> { let tls_builder = NTlsConnector::builder().danger_accept_invalid_certs(true).build()?; let tls_connector = TlsConnector::from(tls_builder); Ok(tls_connector.connect(&config.server, tcp_stream).await?) } -async fn handler(tls_stream: tokio_native_tls::TlsStream, config: &Config) -> Result<(), Box> { -//async fn handler(mut tls_stream: tokio_native_tls::TlsStream, config: &Config) -> Result<(), Box> { - let (mut reader, mut writer) = split(tls_stream); - let (tx, mut rx) = mpsc::channel(1000); +/// Handle the connection to the server +async fn handler(tls_stream: tokio_native_tls::TlsStream, config: Config) -> Result<(), Box> { + let (reader, writer) = split(tls_stream); + let (tx, rx) = mpsc::channel(1000); + + + let read_task = tokio::spawn(async move { - let mut buf = vec![0; 4096]; - while let Ok(n) = reader.read(&mut buf).await { - if n == 0 { break; } // connection killed x.x - let msg = String::from_utf8_lossy(&buf[..n]).to_string(); - if tx.send(msg).await.is_err() { break; } // channel killed x.x - } + readmsg(reader, tx).await; }); - //let read_task = tokio::spawn(async move { - // let mut buf = vec![0; 4096]; - // loop { - // let n = match reader.read(&mut buf).await { - // Ok(0) => return, // connection killed x.x - // Ok(n) => n, - // Err(e) => { - // eprintln!("Error reading from socket: {:?}", e); - // return; - // }, - // }; - // - // let msg = String::from_utf8_lossy(&buf[..n]).to_string(); - // if tx.send(msg).await.is_err() { - // eprintln!("Error sending message to the channel"); - // return; - // } - // } - //}); - // + let write_task = tokio::spawn(async move { - while let Some(msg) = rx.recv().await { - // new commands here - if msg.starts_with("PING") { - writer.write_all(format!("PONG {}\r\n", &msg[5..]).as_bytes()).await.unwrap(); - } - } + writemsg(writer, rx, &config).await; }); - //let write_task = tokio::spawn(async move { - // while let Some(msg) = rx.recv().await { - // if msg.starts_with("PING") { - // writer.write_all(format!("PONG {}\r\n", &msg[5..]).as_bytes()).await.unwrap(); - // } - // if let Some(username) = &config.sasl_username { - // if let Some(password) = &config.sasl_password { - // handle_sasl_messages(&mut writer, &msg, username, password).await.unwrap(); - // } - // } - // } - //}); - let _ = tokio::try_join!(read_task, write_task); - + Ok(()) } +/// Read messages from the server +async fn readmsg(mut reader: tokio::io::ReadHalf>, tx: tokio::sync::mpsc::Sender) { + let mut buf = vec![0; 4096]; + while let Ok (n) = reader.read(&mut buf).await { + if n == 0 { break; } + let msg = String::from_utf8_lossy(&buf[..n]).to_string(); + // must pretty this up later + println!{"{}{}{} {}{} {}", "[".green().bold(), ">".yellow().bold(), "]".green().bold(), "DEBUG:".bold().yellow(), ":".bold().green(), msg.purple()}; + + tx.send(msg).await.unwrap(); + } +} + +/// Write messages to the server +async fn writemsg(mut writer: tokio::io::WriteHalf>, mut rx: tokio::sync::mpsc::Receiver, config: &Config) { + // sasl auth + let capabilities = config.capabilities.clone(); + let username = config.sasl_username.clone().unwrap(); + let password = config.sasl_password.clone().unwrap(); + let nickname = config.nickname.clone(); + + + if !password.is_empty() { + println!("Starting SASL auth..."); + start_sasl_auth(&mut writer, "PLAIN", &nickname, capabilities).await.unwrap(); + writer.flush().await.unwrap(); + } else { + nickme(&mut writer, &nickname).await.unwrap(); + } + + writer.flush().await.unwrap(); + // THIS NEEDS TO BE REBUILT TO BE MORE MODULAR AND SECURE + while let Some(msg) = rx.recv().await { + + if msg.starts_with("PING") { + let response = msg.replace("PING", "PONG"); + println!("{} {} {}","[%] PONG:".bold().green(), nickname.blue(), response.purple()); + writer.write_all(response.as_bytes()).await.unwrap(); + writer.flush().await.unwrap(); + //continue; + } + // handle sasl auth + if !password.is_empty(){ + println!("Handling SASL messages..."); + handle_sasl_messages(&mut writer, &msg, &username, &password, &nickname).await.unwrap(); + //continue; + writer.flush().await.unwrap(); + } + + // new commands here + if msg.contains("001") { + println!("Setting mode"); + writer.write_all(format!("MODE {} +B\r\n", nickname).as_bytes()).await.unwrap(); + writer.flush().await.unwrap(); + } + + + if msg.contains("433") { + println!("Nickname already in use, appending _ to nickname"); + let new_nick = format!("{}_", nickname); + nickme(&mut writer, &new_nick).await.unwrap(); + writer.flush().await.unwrap(); + } + if msg.contains("376") { + println!("Joining channel"); + writer.write_all(format!("JOIN {}\r\n", config.channel).as_bytes()).await.unwrap(); + writer.flush().await.unwrap(); + } + + } +} + +async fn nickme(writer: &mut W, nickname: &str) -> Result<(), Box> { + writer.write_all(format!("NICK {}\r\n", nickname).as_bytes()).await?; + writer.flush().await?; + writer.write_all(format!("USER {} 0 * :{}\r\n", nickname, nickname).as_bytes()).await?; + writer.flush().await?; + Ok(()) +} diff --git a/src/mods/sasl.rs b/src/mods/sasl.rs index 020c1ba..0ac0bdd 100644 --- a/src/mods/sasl.rs +++ b/src/mods/sasl.rs @@ -1,29 +1,23 @@ // mods/sasl.rs use base64::Engine; -use tokio::io::AsyncWriteExt; -/// Sends the initial commands to negotiate capabilities and start SASL authentication. pub async fn start_sasl_auth( -//pub async fn start_sasl_auth(...) -> Result<(), Box> { writer: &mut W, mechanism: &str, nickname: &str, - capabilities: &[String], // Add a parameter for capabilities -) -> Result<(), Box> { - // Request a list of capabilities from the server + capabilities: Option>) -> Result<(), Box> { writer.write_all(b"CAP LS 302\r\n").await?; - // Send NICK and USER commands let nick_cmd = format!("NICK {}\r\n", nickname); writer.write_all(nick_cmd.as_bytes()).await?; let user_cmd = format!("USER {} 0 * :{}\r\n", nickname, nickname); writer.write_all(user_cmd.as_bytes()).await?; - // Request specific capabilities, including 'sasl' for SASL authentication - if !capabilities.is_empty() { - let cap_req_cmd = format!("CAP REQ :{}\r\n", capabilities.join(" ")); - writer.write_all(cap_req_cmd.as_bytes()).await?; + if let Some(caps) = capabilities { + if !caps.is_empty() { + let cap_req_cmd = format!("CAP REQ :{}\r\n", caps.join(" ")); + writer.write_all(cap_req_cmd.as_bytes()).await?; + } } else { - // If no specific capabilities are requested, directly request 'sasl' writer.write_all(b"CAP REQ :sasl\r\n").await?; } @@ -31,15 +25,15 @@ pub async fn start_sasl_auth( Ok(()) } -/// Continues the SASL authentication process based on the server's responses. -//pub async fn handle_sasl_messages(...) -> Result<(), Box> { pub async fn handle_sasl_messages( writer: &mut W, message: &str, username: &str, password: &str, + nickname: &str, ) -> Result<(), Box> { - if message.contains("CAP * ACK :sasl") { + let nick = format!("CAP {} ACK :sasl", nickname.to_string()); + if message.contains(&nick) { writer.write_all(b"AUTHENTICATE PLAIN\r\n").await?; } else if message.starts_with("AUTHENTICATE +") { let auth_string = format!("\0{}\0{}", username, password); -- 2.39.5 From 85ce276658e0be476a0178c02956b8ca8263b690 Mon Sep 17 00:00:00 2001 From: sad Date: Tue, 5 Mar 2024 03:35:55 -0700 Subject: [PATCH 06/19] fix sasl and user input fixes --- src/main.rs | 95 ++++++++++++++++++++++++++++-------------------- src/mods/sasl.rs | 12 ++---- 2 files changed, 59 insertions(+), 48 deletions(-) diff --git a/src/main.rs b/src/main.rs index 190d4b3..28a825f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,9 +4,8 @@ use tokio_native_tls::native_tls::TlsConnector as NTlsConnector; use tokio_native_tls::TlsConnector; use tokio::sync::mpsc; use serde::Deserialize; -use tokio_rustls::rustls::Writer; use std::fs; -use std::future::IntoFuture; +use std::sync::atomic::{AtomicBool, Ordering}; use colored::*; #[derive(Deserialize)] @@ -28,24 +27,25 @@ use mods::sasl::{start_sasl_auth, handle_sasl_messages}; #[tokio::main(flavor = "multi_thread", worker_threads = 12)] async fn main() -> Result<(), Box> { + tokio::spawn(async move { println!("Loading Config..."); let config = loaded_config().expect("Error parsing config.toml"); println!("Config loaded!"); if config.use_ssl { - let tcp_stream = TcpStream::connect(format!("{}:{}", config.server, config.port)).await?; + let tcp_stream = TcpStream::connect(format!("{}:{}", config.server, config.port)).await; println!("Connected to {}!", format!("{}:{}", config.server, config.port).green()); println!("Establishing TLS connection..."); - let mut tls_stream = tls_exec (&config, tcp_stream).await?; + let mut tls_stream = tls_exec (&config, tcp_stream.unwrap()).await.unwrap(); println!("TLS connection established!"); - tls_stream.flush().await?; + tls_stream.flush().await.unwrap(); - handler(tls_stream, config).await?; + handler(tls_stream, config).await.unwrap(); } else { println!("Non-SSL connection not implemented."); } - + }).await.unwrap(); Ok(()) } /// Load the config file @@ -89,73 +89,88 @@ async fn readmsg(mut reader: tokio::io::ReadHalf".yellow().bold(), "]".green().bold(), "DEBUG:".bold().yellow(), ":".bold().green(), msg.purple()}; - - tx.send(msg).await.unwrap(); + let msg_list = String::from_utf8_lossy(&buf[..n]).to_string(); + for lines in msg_list.lines() { + let msg = lines.to_string(); + println!("{}{}{} {}{} {}", "[".green().bold(), ">".yellow().bold(), "]".green().bold(), "DEBUG:".bold().yellow(), ":".bold().green(), msg.trim().purple()); + tx.send(msg).await.unwrap(); + if buf.len() == n { + buf.resize(buf.len() * 2, 0); + } + } } } + + +static SASL_AUTH: AtomicBool = AtomicBool::new(false); /// Write messages to the server async fn writemsg(mut writer: tokio::io::WriteHalf>, mut rx: tokio::sync::mpsc::Receiver, config: &Config) { // sasl auth - let capabilities = config.capabilities.clone(); + //let capabilities = config.capabilities.clone(); let username = config.sasl_username.clone().unwrap(); let password = config.sasl_password.clone().unwrap(); let nickname = config.nickname.clone(); - - - if !password.is_empty() { + if !password.is_empty() && !SASL_AUTH.load(Ordering::Relaxed) { + let capabilities = config.capabilities.clone(); println!("Starting SASL auth..."); start_sasl_auth(&mut writer, "PLAIN", &nickname, capabilities).await.unwrap(); writer.flush().await.unwrap(); + SASL_AUTH.store(true, Ordering::Relaxed); } else { nickme(&mut writer, &nickname).await.unwrap(); + writer.flush().await.unwrap(); } + //writer.flush().await.unwrap(); + //let msg = rx.recv().await.unwrap(); + //let msg = msg.trim(); + //let parts = msg.split(' ').collect::>(); - writer.flush().await.unwrap(); // THIS NEEDS TO BE REBUILT TO BE MORE MODULAR AND SECURE while let Some(msg) = rx.recv().await { + let msg = msg.trim(); + if msg.is_empty() { + continue; + } + let parts = msg.split(' ').collect::>(); + let serv = parts.first().unwrap_or(&""); + let cmd = parts.get(1).unwrap_or(&""); - if msg.starts_with("PING") { - let response = msg.replace("PING", "PONG"); + + + println!("{} {} {} {} {}", "DEBUG:".bold().yellow(), "serv:".bold().green(), serv.purple(), "cmd:".bold().green(), cmd.purple()); + if *serv == "PING" { + let response = msg.replace("PING", "PONG") + "\r\n"; println!("{} {} {}","[%] PONG:".bold().green(), nickname.blue(), response.purple()); writer.write_all(response.as_bytes()).await.unwrap(); writer.flush().await.unwrap(); - //continue; + continue; } - // handle sasl auth - if !password.is_empty(){ + if (*cmd == "CAP" || msg.starts_with("AUTHENTICATE +") || *cmd == "903") && SASL_AUTH.load(Ordering::Relaxed) { println!("Handling SASL messages..."); - handle_sasl_messages(&mut writer, &msg, &username, &password, &nickname).await.unwrap(); - //continue; + handle_sasl_messages(&mut writer, msg.trim(), &username, &password, &nickname).await.unwrap(); writer.flush().await.unwrap(); - } - - // new commands here - if msg.contains("001") { + } + if *cmd == "001" { println!("Setting mode"); writer.write_all(format!("MODE {} +B\r\n", nickname).as_bytes()).await.unwrap(); writer.flush().await.unwrap(); } - - - if msg.contains("433") { - println!("Nickname already in use, appending _ to nickname"); - let new_nick = format!("{}_", nickname); - nickme(&mut writer, &new_nick).await.unwrap(); - writer.flush().await.unwrap(); - } - if msg.contains("376") { + + if *cmd == "376" { println!("Joining channel"); writer.write_all(format!("JOIN {}\r\n", config.channel).as_bytes()).await.unwrap(); writer.flush().await.unwrap(); } - - } + if *cmd == "PRIVMSG" { + let channel = parts[2]; + let user = parts[0]; + let host = user.split_at(user.find('!').unwrap()); + let msg = parts[3..].join(" ").replace(':', ""); + println!("{}{}{} {}{} {} {} {}", "[".green().bold(), ">".yellow().bold(), "]".green().bold(), "PRIVMSG:".bold().yellow(), ":".bold().green(), channel.yellow(), user.blue(), msg.purple()); + } + } } - async fn nickme(writer: &mut W, nickname: &str) -> Result<(), Box> { writer.write_all(format!("NICK {}\r\n", nickname).as_bytes()).await?; writer.flush().await?; diff --git a/src/mods/sasl.rs b/src/mods/sasl.rs index 0ac0bdd..bdef9d8 100644 --- a/src/mods/sasl.rs +++ b/src/mods/sasl.rs @@ -1,4 +1,5 @@ // mods/sasl.rs +use crate::nickme; use base64::Engine; pub async fn start_sasl_auth( writer: &mut W, @@ -7,10 +8,7 @@ pub async fn start_sasl_auth( capabilities: Option>) -> Result<(), Box> { writer.write_all(b"CAP LS 302\r\n").await?; - let nick_cmd = format!("NICK {}\r\n", nickname); - writer.write_all(nick_cmd.as_bytes()).await?; - let user_cmd = format!("USER {} 0 * :{}\r\n", nickname, nickname); - writer.write_all(user_cmd.as_bytes()).await?; + nickme(writer, nickname).await?; if let Some(caps) = capabilities { if !caps.is_empty() { @@ -20,7 +18,7 @@ pub async fn start_sasl_auth( } else { writer.write_all(b"CAP REQ :sasl\r\n").await?; } - + //println!("Handling SASL messages..."); writer.flush().await?; Ok(()) } @@ -32,8 +30,7 @@ pub async fn handle_sasl_messages( password: &str, nickname: &str, ) -> Result<(), Box> { - let nick = format!("CAP {} ACK :sasl", nickname.to_string()); - if message.contains(&nick) { + if message.contains(format!("CAP {} ACK :sasl", nickname).as_str()) { writer.write_all(b"AUTHENTICATE PLAIN\r\n").await?; } else if message.starts_with("AUTHENTICATE +") { let auth_string = format!("\0{}\0{}", username, password); @@ -45,4 +42,3 @@ pub async fn handle_sasl_messages( writer.flush().await?; Ok(()) } - -- 2.39.5 From 8853d177236f3154e29b90a43ab07e053265082c Mon Sep 17 00:00:00 2001 From: sad Date: Wed, 6 Mar 2024 03:27:32 -0700 Subject: [PATCH 07/19] add sed command --- Cargo.toml | 1 + src/main.rs | 44 +++++++++++++++++++------------ src/mods/sed.rs | 70 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+), 17 deletions(-) create mode 100644 src/mods/sed.rs diff --git a/Cargo.toml b/Cargo.toml index 46a9916..d5cc9b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ native-tls = "0.2.11" rand = "0.8.5" toml = "0.8.10" base64 = "0.22.0" +regex = "1.10.3" serde = { version = "1.0.197", features = ["derive"] } colored = "2.1.0" futures = "0.3.30" diff --git a/src/main.rs b/src/main.rs index 28a825f..456cc57 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,8 +22,11 @@ struct Config { mod mods { pub mod sasl; + pub mod sed; } use mods::sasl::{start_sasl_auth, handle_sasl_messages}; +use mods::sed::{SedCommand, MessageBuffer}; + #[tokio::main(flavor = "multi_thread", worker_threads = 12)] async fn main() -> Result<(), Box> { @@ -48,6 +51,7 @@ async fn main() -> Result<(), Box> { }).await.unwrap(); Ok(()) } + /// Load the config file fn loaded_config() -> Result> { let config_contents = fs::read_to_string("config.toml")?; @@ -67,16 +71,15 @@ async fn tls_exec(config: &Config, tcp_stream: TcpStream) -> Result, config: Config) -> Result<(), Box> { let (reader, writer) = split(tls_stream); let (tx, rx) = mpsc::channel(1000); - - let read_task = tokio::spawn(async move { readmsg(reader, tx).await; }); + let message_buffer = MessageBuffer::new(1000); let write_task = tokio::spawn(async move { - writemsg(writer, rx, &config).await; + writemsg(writer, rx, &config, message_buffer).await; }); let _ = tokio::try_join!(read_task, write_task); @@ -104,10 +107,10 @@ async fn readmsg(mut reader: tokio::io::ReadHalf>, mut rx: tokio::sync::mpsc::Receiver, config: &Config) { - // sasl auth - //let capabilities = config.capabilities.clone(); +async fn writemsg(mut writer: tokio::io::WriteHalf>, mut rx: tokio::sync::mpsc::Receiver, config: &Config, mut message_buffer: MessageBuffer) { + let username = config.sasl_username.clone().unwrap(); let password = config.sasl_password.clone().unwrap(); let nickname = config.nickname.clone(); @@ -121,12 +124,7 @@ async fn writemsg(mut writer: tokio::io::WriteHalf>(); - // THIS NEEDS TO BE REBUILT TO BE MORE MODULAR AND SECURE while let Some(msg) = rx.recv().await { let msg = msg.trim(); if msg.is_empty() { @@ -136,8 +134,6 @@ async fn writemsg(mut writer: tokio::io::WriteHalf".yellow().bold(), "]".green().bold(), "PRIVMSG:".bold().yellow(), ":".bold().green(), channel.yellow(), user.blue(), msg.purple()); + let user = parts[0].strip_prefix(':').unwrap().split_at(parts[0].find('!').unwrap()).0; + let host = parts[0].split_at(parts[0].find('!').unwrap()).1; + let msg_content = parts[3..].join(" ").replace(':', ""); + println!("{} {} {} {} {} {} {}", "DEBUG:".bold().yellow(), "channel:".bold().green(), channel.purple(), "user:".bold().green(), host.purple(), "msg:".bold().green(), msg_content.purple()); + // sed + if msg_content.starts_with("s/") { + println!("Sed command detected"); + if let Some(sed_command) = SedCommand::parse(&msg_content) { + if let Some(response) = message_buffer.apply_sed_command(&sed_command) { + writer.write_all(format!("PRIVMSG {} :{}: {}\r\n", channel, user, response).as_bytes()).await.unwrap(); + writer.flush().await.unwrap(); + } + } + } else { + message_buffer.add_message(msg_content); + } + // other commands here } } } @@ -178,3 +187,4 @@ async fn nickme(writer: &mut W, nickname: & writer.flush().await?; Ok(()) } + diff --git a/src/mods/sed.rs b/src/mods/sed.rs new file mode 100644 index 0000000..808ad3e --- /dev/null +++ b/src/mods/sed.rs @@ -0,0 +1,70 @@ +use regex::Regex; +use std::collections::VecDeque; + +pub struct SedCommand { + pattern: Regex, + replacement: String, + global: bool, +} + +impl SedCommand { + pub fn parse(command: &str) -> Option { + let parts: Vec<&str> = command.split('/').collect(); + if parts.len() >= 4 { + let pattern = match Regex::new(parts[1]) { + Ok(regex) => regex, + Err(_) => return None, + }; + let replacement = parts[2].to_string(); + let global = parts.get(3).map_or(false, |&flag| flag.contains('g')); + + Some(SedCommand { + pattern, + replacement, + global, + }) + } else { + None + } + } + + pub fn apply_to(&self, message: &str) -> String { + if self.global { + self.pattern.replace_all(message, self.replacement.as_str()).to_string() + } else { + self.pattern.replace(message, self.replacement.as_str()).to_string() + } + } +} + +pub struct MessageBuffer { + buffer: VecDeque, + capacity: usize, +} + +impl MessageBuffer { + pub fn new(capacity: usize) -> Self { + MessageBuffer { + buffer: VecDeque::with_capacity(capacity), + capacity, + } + } + + pub fn add_message(&mut self, message: String) { + if self.buffer.len() == self.capacity { + self.buffer.pop_front(); + } + self.buffer.push_back(message); + } + + pub fn apply_sed_command(&mut self, command: &SedCommand) -> Option { + for message in self.buffer.iter_mut() { + if command.pattern.is_match(message) { + *message = command.apply_to(message); + return Some(message.clone()); + } + } + None + } +} + -- 2.39.5 From 322f473671adc736360358a2ab0bee5bd96655b7 Mon Sep 17 00:00:00 2001 From: sad Date: Wed, 6 Mar 2024 03:31:54 -0700 Subject: [PATCH 08/19] fix user parsing --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 456cc57..688dbc5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -160,7 +160,7 @@ async fn writemsg(mut writer: tokio::io::WriteHalf Date: Mon, 11 Mar 2024 22:12:42 -0600 Subject: [PATCH 09/19] add proxy support --- config.toml | 12 +++++-- src/main.rs | 99 ++++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 95 insertions(+), 16 deletions(-) diff --git a/config.toml b/config.toml index 1bc452f..15883e5 100644 --- a/config.toml +++ b/config.toml @@ -2,7 +2,13 @@ server = "irc.supernets.org" port = 6697 use_ssl = true nickname = "g1r" -channel = "#dev" -sasl_username = "g1r" -sasl_password = "fuckyou.lol" +channel = "#superbowl" +sasl_username = "" +sasl_password = "" capabilities = ["sasl"] +use_proxy = false +proxy_type = "socks5" +proxy_addr = "127.0.0.1" +proxy_port = 9050 +proxy_username = "" +proxy_password = "" diff --git a/src/main.rs b/src/main.rs index 688dbc5..62133bd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -use tokio::io::{split, AsyncReadExt, AsyncWriteExt}; +use tokio::io::{split, AsyncRead, AsyncWrite, AsyncReadExt, AsyncWriteExt}; use tokio::net::TcpStream; use tokio_native_tls::native_tls::TlsConnector as NTlsConnector; use tokio_native_tls::TlsConnector; @@ -7,6 +7,7 @@ use serde::Deserialize; use std::fs; use std::sync::atomic::{AtomicBool, Ordering}; use colored::*; +use tokio_socks::tcp::Socks5Stream; #[derive(Deserialize)] struct Config { @@ -18,6 +19,14 @@ struct Config { sasl_username: Option, sasl_password: Option, capabilities: Option>, + + // Proxy + use_proxy: bool, + proxy_type: Option, + proxy_addr: Option, + proxy_port: Option, + proxy_username: Option, + proxy_password: Option, } mod mods { @@ -35,20 +44,57 @@ async fn main() -> Result<(), Box> { let config = loaded_config().expect("Error parsing config.toml"); println!("Config loaded!"); + let server = format!("{}:{}", config.server, config.port); + if config.use_ssl { - let tcp_stream = TcpStream::connect(format!("{}:{}", config.server, config.port)).await; - println!("Connected to {}!", format!("{}:{}", config.server, config.port).green()); + if config.use_proxy { + let tcp_stream = proxy_exec(&config).await; + match tcp_stream { + Ok(tcp_stream) => { + let tls_stream = tls_exec(&config, tcp_stream).await; + match tls_stream { + Ok(tls_stream) => { + if let Err(e) = handler(tls_stream, config).await { + println!("Error handling TLS connection: {}", e); + } + }, + Err(e) => { + println!("Error establishing TLS connection: {}", e); + } + } + //handler(tls_stream, config).await.unwrap(); + }, + Err(e) => { + println!("Error connecting to proxy: {}", e); + } + } + //let tls_stream = tls_exec(&config, tcp_stream).await + //handler(tls_stream, config).await.unwrap(); + } else { + let tcp_stream = TcpStream::connect(server).await.expect("Error connecting to server"); + let tls_stream = tls_exec(&config, tcp_stream).await.expect("Error establishing TLS connection"); + handler(tls_stream, config).await.unwrap(); + } + //let tcp_stream = TcpStream::connect(format!("{}:{}", config.server, config.port)).await; + //println!("Connected to {}!", format!("{}:{}", config.server, config.port).green()); + //println!("Establishing TLS connection..."); + //if let Ok(tcp_stream) = TcpStream::connect(format!("{}:{}", config.server, config.port)).await { + // println!("TCP connection established!"); + // let mut tls_stream = tls_exec(&config, Some(tcp_stream)).await.unwrap(); + // println!("TLS connection established!"); + // tls_stream.flush().await.unwrap(); + //} else { + // println!("TCP connection failed!"); + //}; + //let mut tls_stream = tls_exec(&config, Some(tcp_stream.unwrap())).await.unwrap(); + //println!("TLS connection established!"); + //tls_stream.flush().await.unwrap(); - println!("Establishing TLS connection..."); - let mut tls_stream = tls_exec (&config, tcp_stream.unwrap()).await.unwrap(); - println!("TLS connection established!"); - tls_stream.flush().await.unwrap(); - - handler(tls_stream, config).await.unwrap(); + //handler(tls_stream, config).await.unwrap(); } else { println!("Non-SSL connection not implemented."); } - }).await.unwrap(); + }).await.unwrap(); Ok(()) } @@ -60,12 +106,39 @@ fn loaded_config() -> Result> { } /// Establish a TLS connection to the server -async fn tls_exec(config: &Config, tcp_stream: TcpStream) -> Result, Box> { - let tls_builder = NTlsConnector::builder().danger_accept_invalid_certs(true).build()?; +async fn tls_exec(config: &Config, tcp_stream: TcpStream) -> Result, Box> { + let tls_builder = NTlsConnector::builder().danger_accept_invalid_certs(true).build().unwrap(); let tls_connector = TlsConnector::from(tls_builder); - Ok(tls_connector.connect(&config.server, tcp_stream).await?) + Ok(tls_connector.connect(&config.server, tcp_stream).await.unwrap()) + } +/// Establish a connection to the proxy +async fn proxy_exec(config: &Config) -> Result> { + let proxy_addr = match config.proxy_addr.as_ref() { + Some(addr) => addr, + None => "127.0.0.1", + }; + let proxy_port = config.proxy_port.unwrap_or(9050); + let proxy = format!("{}:{}", proxy_addr, proxy_port); + let server = format!("{}:{}", config.server, config.port); + let proxy_stream = TcpStream::connect(proxy).await.unwrap(); + let username = config.proxy_username.clone().unwrap(); + let password = config.proxy_password.clone().unwrap(); + let tcp_stream = if !&username.is_empty() && !password.is_empty() { + let tcp_stream = Socks5Stream::connect_with_password_and_socket(proxy_stream, server, &username, &password).await.unwrap(); + tcp_stream + + } else { + let tcp_stream = Socks5Stream::connect_with_socket(proxy_stream, server).await.unwrap(); + tcp_stream + }; + let tcp_stream = tcp_stream.into_inner(); + +// ok(tcp_stream) + //Ok(tcp_stream) + Ok(tcp_stream) +} /// Handle the connection to the server async fn handler(tls_stream: tokio_native_tls::TlsStream, config: Config) -> Result<(), Box> { -- 2.39.5 From d74d8184c387fa7af73fca65e2a7a655d7a70b21 Mon Sep 17 00:00:00 2001 From: sad Date: Mon, 11 Mar 2024 22:14:29 -0600 Subject: [PATCH 10/19] add proxy support --- src/main.rs | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/src/main.rs b/src/main.rs index 62133bd..d840741 100644 --- a/src/main.rs +++ b/src/main.rs @@ -62,35 +62,16 @@ async fn main() -> Result<(), Box> { println!("Error establishing TLS connection: {}", e); } } - //handler(tls_stream, config).await.unwrap(); }, Err(e) => { println!("Error connecting to proxy: {}", e); } } - //let tls_stream = tls_exec(&config, tcp_stream).await - //handler(tls_stream, config).await.unwrap(); } else { let tcp_stream = TcpStream::connect(server).await.expect("Error connecting to server"); let tls_stream = tls_exec(&config, tcp_stream).await.expect("Error establishing TLS connection"); handler(tls_stream, config).await.unwrap(); } - //let tcp_stream = TcpStream::connect(format!("{}:{}", config.server, config.port)).await; - //println!("Connected to {}!", format!("{}:{}", config.server, config.port).green()); - //println!("Establishing TLS connection..."); - //if let Ok(tcp_stream) = TcpStream::connect(format!("{}:{}", config.server, config.port)).await { - // println!("TCP connection established!"); - // let mut tls_stream = tls_exec(&config, Some(tcp_stream)).await.unwrap(); - // println!("TLS connection established!"); - // tls_stream.flush().await.unwrap(); - //} else { - // println!("TCP connection failed!"); - //}; - //let mut tls_stream = tls_exec(&config, Some(tcp_stream.unwrap())).await.unwrap(); - //println!("TLS connection established!"); - //tls_stream.flush().await.unwrap(); - - //handler(tls_stream, config).await.unwrap(); } else { println!("Non-SSL connection not implemented."); } @@ -135,8 +116,6 @@ async fn proxy_exec(config: &Config) -> Result) Ok(tcp_stream) } -- 2.39.5 From c70eda96b898ec717ae7a9be14b8b32459af1077 Mon Sep 17 00:00:00 2001 From: sad Date: Fri, 15 Mar 2024 01:42:01 -0600 Subject: [PATCH 11/19] add in ascii support --- config.toml | 20 ++++-- src/main.rs | 71 ++++++++++++++------- src/mods/ascii.rs | 157 ++++++++++++++++++++++++++++++++++++++++++++++ src/mods/sasl.rs | 3 +- 4 files changed, 224 insertions(+), 27 deletions(-) create mode 100644 src/mods/ascii.rs diff --git a/config.toml b/config.toml index 15883e5..6e1a529 100644 --- a/config.toml +++ b/config.toml @@ -1,14 +1,26 @@ -server = "irc.supernets.org" +#[server] +server = "198.98.52.138" #"irc.supernets.org" port = 6697 use_ssl = true + +#[user] nickname = "g1r" -channel = "#superbowl" +realname = "git.supernets.org/sad/g1r" +channels = ["#dev", "#superbowl", "#5000"] sasl_username = "" sasl_password = "" capabilities = ["sasl"] + +#[proxy] use_proxy = false proxy_type = "socks5" proxy_addr = "127.0.0.1" -proxy_port = 9050 -proxy_username = "" +proxy_port = 1080 +proxy_username = "" proxy_password = "" + +#[features] +kickrejoin = true +ascii_art = "./ircart/ircart" +pump_delay = 0 # in milliseconds + diff --git a/src/main.rs b/src/main.rs index d840741..869c543 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,11 @@ -use tokio::io::{split, AsyncRead, AsyncWrite, AsyncReadExt, AsyncWriteExt}; +use tokio::io::{split, AsyncRead, AsyncWrite, AsyncReadExt, AsyncWriteExt, BufReader, AsyncBufReadExt}; use tokio::net::TcpStream; use tokio_native_tls::native_tls::TlsConnector as NTlsConnector; use tokio_native_tls::TlsConnector; use tokio::sync::mpsc; use serde::Deserialize; use std::fs; + use std::sync::atomic::{AtomicBool, Ordering}; use colored::*; use tokio_socks::tcp::Socks5Stream; @@ -15,11 +16,12 @@ struct Config { port: u16, use_ssl: bool, nickname: String, - channel: String, + realname: Option, + channels: Vec, sasl_username: Option, sasl_password: Option, capabilities: Option>, - + // Proxy use_proxy: bool, proxy_type: Option, @@ -27,15 +29,19 @@ struct Config { proxy_port: Option, proxy_username: Option, proxy_password: Option, + + ascii_art: Option, + pump_delay: u64, } mod mods { pub mod sasl; pub mod sed; + pub mod ascii; } use mods::sasl::{start_sasl_auth, handle_sasl_messages}; use mods::sed::{SedCommand, MessageBuffer}; - +use mods::ascii::handle_ascii_command; #[tokio::main(flavor = "multi_thread", worker_threads = 12)] async fn main() -> Result<(), Box> { @@ -106,10 +112,9 @@ async fn proxy_exec(config: &Config) -> Result(writer: &mut W, nickname: &str) -> Result<(), Box> { + + + +async fn nickme(writer: &mut W, nickname: &str, realname: &str) -> Result<(), Box> { writer.write_all(format!("NICK {}\r\n", nickname).as_bytes()).await?; writer.flush().await?; - writer.write_all(format!("USER {} 0 * :{}\r\n", nickname, nickname).as_bytes()).await?; + writer.write_all(format!("USER {} 0 * :{}\r\n", nickname, realname).as_bytes()).await?; writer.flush().await?; Ok(()) } diff --git a/src/mods/ascii.rs b/src/mods/ascii.rs new file mode 100644 index 0000000..cb151b8 --- /dev/null +++ b/src/mods/ascii.rs @@ -0,0 +1,157 @@ +use tokio::io::{AsyncWriteExt, BufReader}; +use tokio::fs::File; +use tokio::time::{self, Duration}; +use std::fs; +use rand::Rng; +use tokio::io::AsyncBufReadExt; +use std::error::Error; +use crate::Config; + +const CHUNK_SIZE: usize = 4096; + +async fn send_ansi_art(writer: &mut W, file_path: &str, pump_delay: u64, channel: &str) -> Result<(), Box> { + let file = File::open(file_path).await?; + let reader = BufReader::new(file); + let mut lines = reader.lines(); + + let mut line_count = 0; + let lines_stream = &mut lines; + while let Ok(Some(_)) = lines_stream.next_line().await { + line_count += 1; + } + let mut pump_delay = Duration::from_millis(pump_delay); + if line_count > 500 && pump_delay < Duration::from_millis(100){ + pump_delay = Duration::from_millis(100); + } + let file = File::open(file_path).await?; + let reader = BufReader::new(file); + let mut lines = reader.lines(); + + + while let Some(line) = lines.next_line().await? { + + if line.len() > CHUNK_SIZE { + for chunk in line.as_bytes().chunks(CHUNK_SIZE) { + writer.write_all(format!("PRIVMSG {} :{}\r\n", channel, String::from_utf8_lossy(chunk)).as_bytes()).await?; + writer.flush().await?; + time::sleep(pump_delay).await; + } + } else { + + writer.write_all(format!("PRIVMSG {} :{}\r\n", channel, line).as_bytes()).await?; + writer.flush().await?; + time::sleep(pump_delay).await; + } + } + Ok(()) +} + +fn select_random_file(dir: &str) -> Option { + let files = fs::read_dir(dir).ok()?.filter_map(|entry| { + let path = entry.ok()?.path(); + if path.is_file() { + path.to_str().map(ToString::to_string) + } else { + None + } + }).collect::>(); + + if files.is_empty() { + None + } else { + let mut rng = rand::thread_rng(); + let index = rng.gen_range(0..files.len()); + files.get(index).cloned() + } +} + +pub async fn handle_ascii_command( + writer: &mut W, + config: &Config, // Adjust the path as necessary to match your project structure + command: &str, + channel: &str, +) -> Result<(), Box> { + let parts: Vec<&str> = command.split_whitespace().collect(); + let command_type = parts.get(1).unwrap_or(&""); + + if *command_type == "random" && parts.len() == 2 { + handle_random(writer, config, channel).await?; + } else if *command_type == "list"{ + handle_list(writer, config, channel, Some(parts.get(3).unwrap_or(&""))).await?; + } else { + handle_specific_file(writer, config, channel, &parts).await?; + } + + Ok(()) +} + +async fn handle_random( + writer: &mut W, + config: &Config, + channel: &str, +) -> Result<(), Box> { + if let Some(dir) = config.ascii_art.as_ref() { + if let Some(random_file) = select_random_file(dir) { + send_ansi_art(writer, &random_file, config.pump_delay, channel).await?; + } else { + writer.write_all(format!("PRIVMSG {} :No files found\r\n", channel).as_bytes()).await?; + } + } + Ok(()) +} + +async fn handle_list( + writer: &mut W, + config: &Config, + channel: &str, + subdirectory: Option<&str>, +) -> Result<(), Box> { + let base_dir = config.ascii_art.clone().unwrap_or_else(|| "ascii_art".to_string()); + let dir = if let Some(subdir) = subdirectory { + format!("{}/{}", base_dir, subdir) + } else { + base_dir + }; + + let entries = fs::read_dir(&dir) + .map_err(|_| "Failed to read directory")? + .filter_map(|entry| entry.ok()) + .map(|entry| { + let path = entry.path(); + let display_name = path.file_name().unwrap_or_default().to_string_lossy().into_owned(); + if path.is_dir() { + format!("{}/", display_name) + } else { + display_name.strip_suffix(".txt").unwrap_or(&display_name).to_string() + } + }) + .collect::>() + .join(", "); + + if entries.is_empty() { + writer.write_all(format!("PRIVMSG {} :No files or directories found\r\n", channel).as_bytes()).await?; + } else { + writer.write_all(format!("PRIVMSG {} :{}\r\n", channel, entries).as_bytes()).await?; + } + + Ok(()) +} + +async fn handle_specific_file( + writer: &mut W, + config: &Config, + channel: &str, + parts: &[&str], +) -> Result<(), Box> { + let file_name = if parts.len() >= 3 { + parts[2..].join(" ") + } else { + parts[2].to_string() + }; + + let file_path = format!("{}/{}.txt", config.ascii_art.clone().unwrap_or_else(|| "ascii_art".to_string()), file_name); + + send_ansi_art(writer, &file_path, config.pump_delay, channel).await +} + + diff --git a/src/mods/sasl.rs b/src/mods/sasl.rs index bdef9d8..5b94b34 100644 --- a/src/mods/sasl.rs +++ b/src/mods/sasl.rs @@ -5,10 +5,11 @@ pub async fn start_sasl_auth( writer: &mut W, mechanism: &str, nickname: &str, + realname: &str, capabilities: Option>) -> Result<(), Box> { writer.write_all(b"CAP LS 302\r\n").await?; - nickme(writer, nickname).await?; + nickme(writer, nickname, realname).await?; if let Some(caps) = capabilities { if !caps.is_empty() { -- 2.39.5 From 5287b548642effbed4c75775b6bdb8aedef2ca5e Mon Sep 17 00:00:00 2001 From: sad Date: Fri, 15 Mar 2024 02:02:03 -0600 Subject: [PATCH 12/19] fix some crashes --- src/main.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 869c543..e67b378 100644 --- a/src/main.rs +++ b/src/main.rs @@ -225,12 +225,16 @@ async fn writemsg(mut writer: tokio::io::WriteHalf 3 { + parts[3..].join(" ").replace(':', "") + } else { + "".to_string() + }; println!("{} {} {} {} {} {} {} {} {}", "DEBUG:".bold().yellow(), "channel:".bold().green(), channel.purple(), "user:".bold().green(), user.purple(), "host:".bold().green(), host.purple(), "msg:".bold().green(), msg_content.yellow()); // sed -- 2.39.5 From 512a406f43d671f9ccdc9d740382a32c0ad40c81 Mon Sep 17 00:00:00 2001 From: sad Date: Tue, 19 Mar 2024 15:00:04 -0600 Subject: [PATCH 13/19] fix ascii select file --- src/mods/ascii.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/mods/ascii.rs b/src/mods/ascii.rs index cb151b8..b9c76a1 100644 --- a/src/mods/ascii.rs +++ b/src/mods/ascii.rs @@ -67,7 +67,7 @@ fn select_random_file(dir: &str) -> Option { pub async fn handle_ascii_command( writer: &mut W, - config: &Config, // Adjust the path as necessary to match your project structure + config: &Config, command: &str, channel: &str, ) -> Result<(), Box> { @@ -143,13 +143,16 @@ async fn handle_specific_file( channel: &str, parts: &[&str], ) -> Result<(), Box> { - let file_name = if parts.len() >= 3 { - parts[2..].join(" ") + println!("{:?}", parts); + let file_name = if parts.len() > 2 { + parts[1..].join(" ").replace(' ', "/") } else { - parts[2].to_string() + parts.get(1).unwrap_or(&"").to_string() }; + println!("{:?}", file_name); let file_path = format!("{}/{}.txt", config.ascii_art.clone().unwrap_or_else(|| "ascii_art".to_string()), file_name); + println!("{:?}", file_path); send_ansi_art(writer, &file_path, config.pump_delay, channel).await } -- 2.39.5 From 06b32db70783a5aba2cda83dd66b76061a8ed497 Mon Sep 17 00:00:00 2001 From: sad Date: Thu, 21 Mar 2024 14:33:43 -0600 Subject: [PATCH 14/19] fix autojoin on kick --- src/main.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index e67b378..01fb1e8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -217,9 +217,9 @@ async fn writemsg(mut writer: tokio::io::WriteHalf Date: Thu, 21 Mar 2024 15:32:57 -0600 Subject: [PATCH 15/19] fix ascii list --- src/mods/ascii.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/mods/ascii.rs b/src/mods/ascii.rs index b9c76a1..a8f6f4b 100644 --- a/src/mods/ascii.rs +++ b/src/mods/ascii.rs @@ -77,7 +77,7 @@ pub async fn handle_ascii_command( if *command_type == "random" && parts.len() == 2 { handle_random(writer, config, channel).await?; } else if *command_type == "list"{ - handle_list(writer, config, channel, Some(parts.get(3).unwrap_or(&""))).await?; + handle_list(writer, config, channel, Some(parts.get(2).unwrap_or(&""))).await?; } else { handle_specific_file(writer, config, channel, &parts).await?; } @@ -104,10 +104,11 @@ async fn handle_list( writer: &mut W, config: &Config, channel: &str, - subdirectory: Option<&str>, + parts: Option<&str> ) -> Result<(), Box> { let base_dir = config.ascii_art.clone().unwrap_or_else(|| "ascii_art".to_string()); - let dir = if let Some(subdir) = subdirectory { + + let dir = if let Some(subdir) = parts { format!("{}/{}", base_dir, subdir) } else { base_dir -- 2.39.5 From 6bf609c1624d1657c9c379bd8e3494da502002f4 Mon Sep 17 00:00:00 2001 From: sad Date: Tue, 26 Mar 2024 15:54:35 -0600 Subject: [PATCH 16/19] added reconnect --- src/main.rs | 70 +++++++++++++++++++++++++++++++---------------- src/mods/ascii.rs | 1 - 2 files changed, 46 insertions(+), 25 deletions(-) diff --git a/src/main.rs b/src/main.rs index 01fb1e8..cc54f3a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,7 +10,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use colored::*; use tokio_socks::tcp::Socks5Stream; -#[derive(Deserialize)] +#[derive(Deserialize, Clone)] struct Config { server: String, port: u16, @@ -22,6 +22,9 @@ struct Config { sasl_password: Option, capabilities: Option>, + reconnect_delay: u64, + reconnect_attempts: u64, + // Proxy use_proxy: bool, proxy_type: Option, @@ -45,43 +48,62 @@ use mods::ascii::handle_ascii_command; #[tokio::main(flavor = "multi_thread", worker_threads = 12)] async fn main() -> Result<(), Box> { - tokio::spawn(async move { + //tokio::spawn(async move { println!("Loading Config..."); let config = loaded_config().expect("Error parsing config.toml"); println!("Config loaded!"); + let mut reconnect_attempts = 0; - let server = format!("{}:{}", config.server, config.port); + while reconnect_attempts < config.reconnect_attempts { + let configc = config.clone(); - if config.use_ssl { - if config.use_proxy { - let tcp_stream = proxy_exec(&config).await; - match tcp_stream { - Ok(tcp_stream) => { - let tls_stream = tls_exec(&config, tcp_stream).await; - match tls_stream { - Ok(tls_stream) => { - if let Err(e) = handler(tls_stream, config).await { - println!("Error handling TLS connection: {}", e); + let server = format!("{}:{}", configc.server, configc.port); + let connection_result = tokio::spawn(async move { + let config = configc.clone(); + if config.use_ssl { + if config.use_proxy { + let tcp_stream = proxy_exec(&config).await; + match tcp_stream { + Ok(tcp_stream) => { + let tls_stream = tls_exec(&config, tcp_stream).await; + match tls_stream { + Ok(tls_stream) => { + if let Err(e) = handler(tls_stream, config).await { + println!("Error handling TLS connection: {}", e); + } + }, + Err(e) => { + println!("Error establishing TLS connection: {}", e); + } } }, Err(e) => { - println!("Error establishing TLS connection: {}", e); + println!("Error connecting to proxy: {}", e); } } - }, - Err(e) => { - println!("Error connecting to proxy: {}", e); + } else { + let tcp_stream = TcpStream::connect(server).await.expect("Error connecting to server"); + let tls_stream = tls_exec(&config, tcp_stream).await.expect("Error establishing TLS connection"); + handler(tls_stream, config).await.unwrap(); } + } else { + println!("Non-SSL connection not implemented."); + } + Ok::<(), Box>(()) + }).await.unwrap(); + match connection_result { + Ok(_) => { + println!("Connection established successfully!"); + reconnect_attempts = 0; + }, + Err(e) => { + println!("Error handling connection: {}", e); + reconnect_attempts += 1; + tokio::time::sleep(tokio::time::Duration::from_secs(config.reconnect_delay)).await; } - } else { - let tcp_stream = TcpStream::connect(server).await.expect("Error connecting to server"); - let tls_stream = tls_exec(&config, tcp_stream).await.expect("Error establishing TLS connection"); - handler(tls_stream, config).await.unwrap(); } - } else { - println!("Non-SSL connection not implemented."); } - }).await.unwrap(); + println!("Reconnect attempts exceeded. Exiting..."); Ok(()) } diff --git a/src/mods/ascii.rs b/src/mods/ascii.rs index a8f6f4b..8353bc0 100644 --- a/src/mods/ascii.rs +++ b/src/mods/ascii.rs @@ -27,7 +27,6 @@ async fn send_ansi_art(writer: &mut W, file_path: &str let reader = BufReader::new(file); let mut lines = reader.lines(); - while let Some(line) = lines.next_line().await? { if line.len() > CHUNK_SIZE { -- 2.39.5 From 80512dd55c90e9080077ef47bbc83525ec9d2b68 Mon Sep 17 00:00:00 2001 From: sad Date: Mon, 1 Apr 2024 22:37:27 -0600 Subject: [PATCH 17/19] add vomit fix msg parse with ':' --- src/main.rs | 25 ++++++++++++--- src/mods/vomit.rs | 77 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 4 deletions(-) create mode 100644 src/mods/vomit.rs diff --git a/src/main.rs b/src/main.rs index cc54f3a..4a48b0e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,8 @@ use tokio::sync::mpsc; use serde::Deserialize; use std::fs; +use rand::{thread_rng, Rng}; + use std::sync::atomic::{AtomicBool, Ordering}; use colored::*; use tokio_socks::tcp::Socks5Stream; @@ -41,10 +43,13 @@ mod mods { pub mod sasl; pub mod sed; pub mod ascii; + pub mod vomit; } use mods::sasl::{start_sasl_auth, handle_sasl_messages}; use mods::sed::{SedCommand, MessageBuffer}; use mods::ascii::handle_ascii_command; +use mods::vomit::{handle_vomit_command}; + #[tokio::main(flavor = "multi_thread", worker_threads = 12)] async fn main() -> Result<(), Box> { @@ -253,7 +258,13 @@ async fn writemsg(mut writer: tokio::io::WriteHalf 3 { - parts[3..].join(" ").replace(':', "") + let remainder = &parts[3..].join(" "); + if let Some(pos) = remainder.find(':') { + let (first_part, last_part) = remainder.split_at(pos); + format!("{}{}", first_part, &last_part[1..]) + } else { + remainder.to_string() + } } else { "".to_string() }; @@ -263,21 +274,24 @@ async fn writemsg(mut writer: tokio::io::WriteHalf(writer: &mut W, nickname: & Ok(()) } + + + diff --git a/src/mods/vomit.rs b/src/mods/vomit.rs new file mode 100644 index 0000000..13f5889 --- /dev/null +++ b/src/mods/vomit.rs @@ -0,0 +1,77 @@ + +use rand::prelude::*; +use tokio::io::AsyncWriteExt; +use tokio::time; +use crate::Config; + +async fn generate_random_unicode() -> char { + let codepoint: u32 = thread_rng().gen_range(0..=0x10FFFF); + std::char::from_u32(codepoint).unwrap_or(' ') +} + +async fn generate_random_color() -> u8 { + thread_rng().gen_range(0..=15) +} + +async fn generate_random_format() -> char { + match thread_rng().gen_range(0..=2) { + 0 => '\x02', // Bold + 1 => '\x1D', // Italic + _ => '\x1F', // Underline + } +} + +async fn generate_vomit(length: usize) -> String { + let mut irc_text = String::new(); + for _ in 0..length { + let char = generate_random_unicode().await; + let color_code = generate_random_color().await; + let format_code = generate_random_format().await; + irc_text.push_str(&format!("\x03{:02}{}", color_code, format_code)); + irc_text.push(char); + } + irc_text +} + +fn split_into_chunks(s: &str, max_chunk_size: usize) -> Vec { + let mut chunks = Vec::new(); + let mut current_chunk = String::new(); + + for char in s.chars() { + if current_chunk.len() + char.len_utf8() > max_chunk_size { + chunks.push(current_chunk.clone()); + current_chunk.clear(); + } + current_chunk.push(char); + } + + if !current_chunk.is_empty() { + chunks.push(current_chunk); + } + + chunks +} + + +const CHUNK_SIZE: usize = 400; +// Function to handle the vomit command +pub async fn handle_vomit_command( + writer: &mut W, + config: &Config, + command: &str, + channel: &str, +) -> Result<(), Box> { + let parts: Vec<&str> = command.split_whitespace().collect(); + let length = parts.get(1).and_then(|s| s.parse().ok()).unwrap_or(1000); + + let vomit = generate_vomit(length).await; // Correctly awaited + let chunks = split_into_chunks(&vomit, CHUNK_SIZE); // Adjust if split_into_chunks is async + + for chunk in chunks { + writer.write_all(format!("PRIVMSG {} :{}\r\n", channel, chunk).as_bytes()).await?; + writer.flush().await?; + time::sleep(tokio::time::Duration::from_secs(config.pump_delay)).await; + } + + Ok(()) +} -- 2.39.5 From 82b809e23945205ee89638ba7113683bfd12ca24 Mon Sep 17 00:00:00 2001 From: sad Date: Thu, 16 May 2024 00:46:01 -0600 Subject: [PATCH 18/19] added non-ssl support --- src/main.rs | 72 ++++++++++++++++++++++++++--------------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/src/main.rs b/src/main.rs index 4a48b0e..1b9ed36 100644 --- a/src/main.rs +++ b/src/main.rs @@ -53,7 +53,6 @@ use mods::vomit::{handle_vomit_command}; #[tokio::main(flavor = "multi_thread", worker_threads = 12)] async fn main() -> Result<(), Box> { - //tokio::spawn(async move { println!("Loading Config..."); let config = loaded_config().expect("Error parsing config.toml"); println!("Config loaded!"); @@ -65,37 +64,40 @@ async fn main() -> Result<(), Box> { let server = format!("{}:{}", configc.server, configc.port); let connection_result = tokio::spawn(async move { let config = configc.clone(); - if config.use_ssl { - if config.use_proxy { - let tcp_stream = proxy_exec(&config).await; - match tcp_stream { - Ok(tcp_stream) => { - let tls_stream = tls_exec(&config, tcp_stream).await; - match tls_stream { - Ok(tls_stream) => { - if let Err(e) = handler(tls_stream, config).await { - println!("Error handling TLS connection: {}", e); - } - }, - Err(e) => { - println!("Error establishing TLS connection: {}", e); - } - } - }, - Err(e) => { - println!("Error connecting to proxy: {}", e); - } + let tcp_stream = if config.use_proxy { + match proxy_exec(&config).await { + Ok(stream) => stream, + Err(e) => { + println!("Error connecting to proxy: {}", e); + return Ok::<(), Box>(()); } - } else { - let tcp_stream = TcpStream::connect(server).await.expect("Error connecting to server"); - let tls_stream = tls_exec(&config, tcp_stream).await.expect("Error establishing TLS connection"); - handler(tls_stream, config).await.unwrap(); } } else { - println!("Non-SSL connection not implemented."); + match TcpStream::connect(server).await { + Ok(stream) => stream, + Err(e) => { + println!("Error connecting to server: {}", e); + return Ok::<(), Box>(()); + } + } + }; + + if config.use_ssl { + println!("Connecting to SSL server..."); + match tls_exec(&config, tcp_stream).await { + Ok(tls_stream) => handler(tls_stream, config).await.unwrap(), + Err(e) => { + println!("Error establishing TLS connection: {}", e); + return Ok::<(), Box>(()); + } + } + } else { + println!("Connecting to Non-SSL server..."); + handler(tcp_stream, config).await.unwrap(); } Ok::<(), Box>(()) }).await.unwrap(); + match connection_result { Ok(_) => { println!("Connection established successfully!"); @@ -139,8 +141,8 @@ async fn proxy_exec(config: &Config) -> Result Result, config: Config) -> Result<(), Box> { - let (reader, writer) = split(tls_stream); +async fn handler(stream: S, config: Config) -> Result<(), Box> where S: AsyncRead + AsyncWrite + Unpin + Send + 'static { + let (reader, writer) = split(stream); let (tx, rx) = mpsc::channel(1000); let read_task = tokio::spawn(async move { @@ -166,13 +168,13 @@ async fn handler(tls_stream: tokio_native_tls::TlsStream, config: Con writemsg(writer, rx, &config, message_buffer).await; }); - let _ = tokio::try_join!(read_task, write_task); - + //let _ = tokio::try_join!(read_task, write_task); + tokio::try_join!(read_task, write_task).map_err(|e| Box::new(e) as Box)?; Ok(()) } /// Read messages from the server -async fn readmsg(mut reader: tokio::io::ReadHalf>, tx: tokio::sync::mpsc::Sender) { +async fn readmsg(mut reader: tokio::io::ReadHalf, tx: tokio::sync::mpsc::Sender) where S: AsyncRead + Unpin { let mut buf = vec![0; 4096]; while let Ok (n) = reader.read(&mut buf).await { if n == 0 { break; } @@ -191,8 +193,7 @@ async fn readmsg(mut reader: tokio::io::ReadHalf>, mut rx: tokio::sync::mpsc::Receiver, config: &Config, mut message_buffer: MessageBuffer) { - +async fn writemsg(mut writer: tokio::io::WriteHalf, mut rx: tokio::sync::mpsc::Receiver, config: &Config, mut message_buffer: MessageBuffer) where S: AsyncWrite + Unpin { let username = config.sasl_username.clone().unwrap(); let password = config.sasl_password.clone().unwrap(); let nickname = config.nickname.clone(); @@ -283,7 +284,6 @@ async fn writemsg(mut writer: tokio::io::WriteHalf Date: Thu, 16 May 2024 02:07:14 -0600 Subject: [PATCH 19/19] mods tls, proxy, handler --- src/main.rs | 74 ++++++++++----------------------------------- src/mods/handler.rs | 24 +++++++++++++++ src/mods/invade.rs | 5 +++ src/mods/proxy.rs | 28 +++++++++++++++++ src/mods/tls.rs | 12 ++++++++ 5 files changed, 85 insertions(+), 58 deletions(-) create mode 100644 src/mods/handler.rs create mode 100644 src/mods/invade.rs create mode 100644 src/mods/proxy.rs create mode 100644 src/mods/tls.rs diff --git a/src/main.rs b/src/main.rs index 1b9ed36..5094c33 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,8 +6,6 @@ use tokio::sync::mpsc; use serde::Deserialize; use std::fs; -use rand::{thread_rng, Rng}; - use std::sync::atomic::{AtomicBool, Ordering}; use colored::*; use tokio_socks::tcp::Socks5Stream; @@ -29,7 +27,7 @@ struct Config { // Proxy use_proxy: bool, - proxy_type: Option, + // proxy_type: Option, proxy_addr: Option, proxy_port: Option, proxy_username: Option, @@ -40,16 +38,23 @@ struct Config { } mod mods { + pub mod proxy; + pub mod tls; + pub mod handler; pub mod sasl; pub mod sed; pub mod ascii; pub mod vomit; +// pub mod invade; } +use mods::proxy::proxy_exec; +use mods::tls::tls_exec; +use mods::handler::handler; use mods::sasl::{start_sasl_auth, handle_sasl_messages}; use mods::sed::{SedCommand, MessageBuffer}; use mods::ascii::handle_ascii_command; -use mods::vomit::{handle_vomit_command}; - +use mods::vomit::handle_vomit_command; +//use mods::invade::{handle_invade_command}; #[tokio::main(flavor = "multi_thread", worker_threads = 12)] async fn main() -> Result<(), Box> { @@ -121,58 +126,6 @@ fn loaded_config() -> Result> { Ok(config) } -/// Establish a TLS connection to the server -async fn tls_exec(config: &Config, tcp_stream: TcpStream) -> Result, Box> { - let tls_builder = NTlsConnector::builder().danger_accept_invalid_certs(true).build().unwrap(); - let tls_connector = TlsConnector::from(tls_builder); - Ok(tls_connector.connect(&config.server, tcp_stream).await.unwrap()) - -} - -/// Establish a connection to the proxy -async fn proxy_exec(config: &Config) -> Result> { - let proxy_addr = match config.proxy_addr.as_ref() { - Some(addr) => addr, - None => "127.0.0.1", - }; - let proxy_port = config.proxy_port.unwrap_or(9050); - let proxy = format!("{}:{}", proxy_addr, proxy_port); - let server = format!("{}:{}", config.server, config.port); - let proxy_stream = TcpStream::connect(proxy).await.unwrap(); - let username = config.proxy_username.clone().unwrap(); - let password = config.proxy_password.clone().unwrap(); - let tcp_stream = if !&username.is_empty() && !password.is_empty() { - let tcp_stream = Socks5Stream::connect_with_password_and_socket(proxy_stream, server, &username, &password).await.unwrap(); - tcp_stream - } else { - let tcp_stream = Socks5Stream::connect_with_socket(proxy_stream, server).await.unwrap(); - tcp_stream - }; - let tcp_stream = tcp_stream.into_inner(); - - Ok(tcp_stream) -} - -/// Handle the connection to the server -async fn handler(stream: S, config: Config) -> Result<(), Box> where S: AsyncRead + AsyncWrite + Unpin + Send + 'static { - let (reader, writer) = split(stream); - let (tx, rx) = mpsc::channel(1000); - - let read_task = tokio::spawn(async move { - readmsg(reader, tx).await; - }); - - let message_buffer = MessageBuffer::new(1000); - - let write_task = tokio::spawn(async move { - writemsg(writer, rx, &config, message_buffer).await; - }); - - //let _ = tokio::try_join!(read_task, write_task); - tokio::try_join!(read_task, write_task).map_err(|e| Box::new(e) as Box)?; - Ok(()) -} - /// Read messages from the server async fn readmsg(mut reader: tokio::io::ReadHalf, tx: tokio::sync::mpsc::Sender) where S: AsyncRead + Unpin { let mut buf = vec![0; 4096]; @@ -288,10 +241,15 @@ async fn writemsg(mut writer: tokio::io::WriteHalf, mut rx: tokio::sync::m let _ = handle_ascii_command(&mut writer, config, &msg_content, channel).await; } - + // vomit if msg_content.starts_with("%vomit") { let _ = handle_vomit_command(&mut writer, config, &msg_content, channel).await; } + + // invade +// if msg_content.starts_with("%invade") { +// let _ = handle_vomit_command(&mut writer, config, &msg_content, channel).await; +// } // other commands here } } diff --git a/src/mods/handler.rs b/src/mods/handler.rs new file mode 100644 index 0000000..4edab29 --- /dev/null +++ b/src/mods/handler.rs @@ -0,0 +1,24 @@ +// mods/handler.rs +use tokio::io::{AsyncRead, AsyncWrite, split}; +use tokio::sync::mpsc; +use crate::{Config, readmsg, writemsg, MessageBuffer}; + +/// Handle the connection to the server +pub async fn handler(stream: S, config: Config) -> Result<(), Box> where S: AsyncRead + AsyncWrite + Unpin + Send + 'static { + let (reader, writer) = split(stream); + let (tx, rx) = mpsc::channel(1000); + + let read_task = tokio::spawn(async move { + readmsg(reader, tx).await; + }); + + let message_buffer = MessageBuffer::new(1000); + + let write_task = tokio::spawn(async move { + writemsg(writer, rx, &config, message_buffer).await; + }); + + //let _ = tokio::try_join!(read_task, write_task); + tokio::try_join!(read_task, write_task).map_err(|e| Box::new(e) as Box)?; + Ok(()) +} diff --git a/src/mods/invade.rs b/src/mods/invade.rs new file mode 100644 index 0000000..85d7028 --- /dev/null +++ b/src/mods/invade.rs @@ -0,0 +1,5 @@ +// mods/invade.rs + + + + diff --git a/src/mods/proxy.rs b/src/mods/proxy.rs new file mode 100644 index 0000000..ca49aa0 --- /dev/null +++ b/src/mods/proxy.rs @@ -0,0 +1,28 @@ +// mods/proxy.rs +use tokio::net::TcpStream; +use tokio_socks::tcp::Socks5Stream; +use crate::Config; + +/// Establish a connection to the proxy +pub async fn proxy_exec(config: &Config) -> Result> { + let proxy_addr = match config.proxy_addr.as_ref() { + Some(addr) => addr, + None => "127.0.0.1", + }; + let proxy_port = config.proxy_port.unwrap_or(9050); + let proxy = format!("{}:{}", proxy_addr, proxy_port); + let server = format!("{}:{}", config.server, config.port); + let proxy_stream = TcpStream::connect(proxy).await.unwrap(); + let username = config.proxy_username.clone().unwrap(); + let password = config.proxy_password.clone().unwrap(); + let tcp_stream = if !&username.is_empty() && !password.is_empty() { + let tcp_stream = Socks5Stream::connect_with_password_and_socket(proxy_stream, server, &username, &password).await.unwrap(); + tcp_stream + } else { + let tcp_stream = Socks5Stream::connect_with_socket(proxy_stream, server).await.unwrap(); + tcp_stream + }; + let tcp_stream = tcp_stream.into_inner(); + + Ok(tcp_stream) +} diff --git a/src/mods/tls.rs b/src/mods/tls.rs new file mode 100644 index 0000000..ddec428 --- /dev/null +++ b/src/mods/tls.rs @@ -0,0 +1,12 @@ +// mods/tls.rs +use tokio::net::TcpStream; +use tokio_native_tls::{TlsConnector, native_tls::TlsConnector as NTlsConnector}; +use crate::Config; + +// Establish a TLS connection to the server +pub async fn tls_exec(config: &Config, tcp_stream: TcpStream) -> Result, Box> { + let tls_builder = NTlsConnector::builder().danger_accept_invalid_certs(true).build().unwrap(); + let tls_connector = TlsConnector::from(tls_builder); + Ok(tls_connector.connect(&config.server, tcp_stream).await.unwrap()) + +} -- 2.39.5