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 + } +} +