WIP: hellfire #1

Draft
sad wants to merge 19 commits from hellfire into main
3 changed files with 98 additions and 17 deletions
Showing only changes of commit 8853d17723 - Show all commits

View File

@ -14,6 +14,7 @@ native-tls = "0.2.11"
rand = "0.8.5" rand = "0.8.5"
toml = "0.8.10" toml = "0.8.10"
base64 = "0.22.0" base64 = "0.22.0"
regex = "1.10.3"
serde = { version = "1.0.197", features = ["derive"] } serde = { version = "1.0.197", features = ["derive"] }
colored = "2.1.0" colored = "2.1.0"
futures = "0.3.30" futures = "0.3.30"

View File

@ -22,8 +22,11 @@ struct Config {
mod mods { mod mods {
pub mod sasl; pub mod sasl;
pub mod sed;
} }
use mods::sasl::{start_sasl_auth, handle_sasl_messages}; use mods::sasl::{start_sasl_auth, handle_sasl_messages};
use mods::sed::{SedCommand, MessageBuffer};
#[tokio::main(flavor = "multi_thread", worker_threads = 12)] #[tokio::main(flavor = "multi_thread", worker_threads = 12)]
async fn main() -> Result<(), Box<dyn std::error::Error>> { async fn main() -> Result<(), Box<dyn std::error::Error>> {
@ -48,6 +51,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
}).await.unwrap(); }).await.unwrap();
Ok(()) Ok(())
} }
/// Load the config file /// Load the config file
fn loaded_config() -> Result<Config, Box<dyn std::error::Error>> { fn loaded_config() -> Result<Config, Box<dyn std::error::Error>> {
let config_contents = fs::read_to_string("config.toml")?; let config_contents = fs::read_to_string("config.toml")?;
@ -68,15 +72,14 @@ async fn handler(tls_stream: tokio_native_tls::TlsStream<TcpStream>, config: Con
let (reader, writer) = split(tls_stream); let (reader, writer) = split(tls_stream);
let (tx, rx) = mpsc::channel(1000); let (tx, rx) = mpsc::channel(1000);
let read_task = tokio::spawn(async move { let read_task = tokio::spawn(async move {
readmsg(reader, tx).await; readmsg(reader, tx).await;
}); });
let message_buffer = MessageBuffer::new(1000);
let write_task = tokio::spawn(async move { 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); let _ = tokio::try_join!(read_task, write_task);
@ -104,10 +107,10 @@ async fn readmsg(mut reader: tokio::io::ReadHalf<tokio_native_tls::TlsStream<Tcp
static SASL_AUTH: AtomicBool = AtomicBool::new(false); static SASL_AUTH: AtomicBool = AtomicBool::new(false);
/// Write messages to the server /// Write messages to the server
async fn writemsg(mut writer: tokio::io::WriteHalf<tokio_native_tls::TlsStream<TcpStream>>, mut rx: tokio::sync::mpsc::Receiver<String>, config: &Config) { async fn writemsg(mut writer: tokio::io::WriteHalf<tokio_native_tls::TlsStream<TcpStream>>, mut rx: tokio::sync::mpsc::Receiver<String>, config: &Config, mut message_buffer: MessageBuffer) {
// sasl auth
//let capabilities = config.capabilities.clone();
let username = config.sasl_username.clone().unwrap(); let username = config.sasl_username.clone().unwrap();
let password = config.sasl_password.clone().unwrap(); let password = config.sasl_password.clone().unwrap();
let nickname = config.nickname.clone(); let nickname = config.nickname.clone();
@ -121,12 +124,7 @@ async fn writemsg(mut writer: tokio::io::WriteHalf<tokio_native_tls::TlsStream<T
nickme(&mut writer, &nickname).await.unwrap(); nickme(&mut writer, &nickname).await.unwrap();
writer.flush().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::<Vec<&str>>();
// THIS NEEDS TO BE REBUILT TO BE MORE MODULAR AND SECURE
while let Some(msg) = rx.recv().await { while let Some(msg) = rx.recv().await {
let msg = msg.trim(); let msg = msg.trim();
if msg.is_empty() { if msg.is_empty() {
@ -136,8 +134,6 @@ async fn writemsg(mut writer: tokio::io::WriteHalf<tokio_native_tls::TlsStream<T
let serv = parts.first().unwrap_or(&""); let serv = parts.first().unwrap_or(&"");
let cmd = parts.get(1).unwrap_or(&""); let cmd = parts.get(1).unwrap_or(&"");
println!("{} {} {} {} {}", "DEBUG:".bold().yellow(), "serv:".bold().green(), serv.purple(), "cmd:".bold().green(), cmd.purple()); println!("{} {} {} {} {}", "DEBUG:".bold().yellow(), "serv:".bold().green(), serv.purple(), "cmd:".bold().green(), cmd.purple());
if *serv == "PING" { if *serv == "PING" {
let response = msg.replace("PING", "PONG") + "\r\n"; let response = msg.replace("PING", "PONG") + "\r\n";
@ -164,10 +160,23 @@ async fn writemsg(mut writer: tokio::io::WriteHalf<tokio_native_tls::TlsStream<T
} }
if *cmd == "PRIVMSG" { if *cmd == "PRIVMSG" {
let channel = parts[2]; let channel = parts[2];
let user = parts[0]; let user = parts[0].strip_prefix(':').unwrap().split_at(parts[0].find('!').unwrap()).0;
let host = user.split_at(user.find('!').unwrap()); let host = parts[0].split_at(parts[0].find('!').unwrap()).1;
let msg = parts[3..].join(" ").replace(':', ""); let msg_content = parts[3..].join(" ").replace(':', "");
println!("{}{}{} {}{} {} {} {}", "[".green().bold(), ">".yellow().bold(), "]".green().bold(), "PRIVMSG:".bold().yellow(), ":".bold().green(), channel.yellow(), user.blue(), msg.purple()); 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<W: tokio::io::AsyncWriteExt + Unpin>(writer: &mut W, nickname: &
writer.flush().await?; writer.flush().await?;
Ok(()) Ok(())
} }

70
src/mods/sed.rs Normal file
View File

@ -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<Self> {
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<String>,
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<String> {
for message in self.buffer.iter_mut() {
if command.pattern.is_match(message) {
*message = command.apply_to(message);
return Some(message.clone());
}
}
None
}
}