WIP: hellfire #1

Draft
sad wants to merge 19 commits from hellfire into main
4 changed files with 96 additions and 7 deletions
Showing only changes of commit e83663ffe5 - Show all commits

View File

@ -13,6 +13,7 @@ tokio-native-tls = "0.3.1"
native-tls = "0.2.11" 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"
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

@ -2,5 +2,7 @@ server = "irc.supernets.org"
port = 6697 port = 6697
use_ssl = true use_ssl = true
nickname = "g1r" nickname = "g1r"
channel = "#superbowl" channel = "#dev"
sasl_username = "g1r"
sasl_password = "fuckyou.lol"
capabilities = ["sasl"]

View File

@ -14,8 +14,16 @@ struct Config {
use_ssl: bool, use_ssl: bool,
nickname: String, nickname: String,
channel: String, channel: String,
sasl_username: Option<String>,
sasl_password: Option<String>,
capabilities: Option<Vec<String>>
} }
mod mods {
pub mod sasl;
}
use mods::sasl::{start_sasl_auth, handle_sasl_messages};
#[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>> {
println!("Loading Config..."); println!("Loading Config...");
@ -39,13 +47,12 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let (reader, writer) = split(tls_stream); let (reader, writer) = split(tls_stream);
let (tx, mut rx) = mpsc::channel(1000); let (tx, mut rx) = mpsc::channel(1000);
// Spawn a task to handle reading
let read_task = tokio::spawn(async move { let read_task = tokio::spawn(async move {
let mut reader = reader; let mut reader = reader;
let mut buf = vec![0; 4096]; let mut buf = vec![0; 4096];
loop { loop {
let n = match reader.read(&mut buf).await { 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, Ok(n) => n,
Err(e) => { Err(e) => {
eprintln!("Error reading from socket: {:?}", e); eprintln!("Error reading from socket: {:?}", e);
@ -58,19 +65,44 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
eprintln!("Error sending message to the channel"); eprintln!("Error sending message to the channel");
return; 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 write_task = tokio::spawn(async move {
let mut writer = writer; 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(); // capabilities
writer.write_all(format!("JOIN {}\r\n", config.channel).as_bytes()).await.unwrap(); //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(); writer.flush().await.unwrap();
while let Some(msg) = rx.recv().await { while let Some(msg) = rx.recv().await {
// handle messages better // handle messages better
println!("{} {}", "[%] DEBUG:".bold().green(), msg.purple()); println!("{} {}", "[%] DEBUG:".bold().green(), msg.purple());
if msg.starts_with("PING") { if msg.starts_with("PING") {
writer.write_all(format!("PONG {}\r\n", &msg[5..]).as_bytes()).await.unwrap(); writer.write_all(format!("PONG {}\r\n", &msg[5..]).as_bytes()).await.unwrap();
} }

54
src/mods/sasl.rs Normal file
View File

@ -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<W: tokio::io::AsyncWriteExt + Unpin>(
//pub async fn start_sasl_auth(...) -> Result<(), Box<dyn std::error::Error>> {
writer: &mut W,
mechanism: &str,
nickname: &str,
capabilities: &[String], // Add a parameter for capabilities
) -> Result<(), Box<dyn std::error::Error>> {
// 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<dyn std::error::Error>> {
pub async fn handle_sasl_messages<W: tokio::io::AsyncWriteExt + Unpin>(
writer: &mut W,
message: &str,
username: &str,
password: &str,
) -> Result<(), Box<dyn std::error::Error>> {
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(())
}