diff --git a/Cargo.toml b/Cargo.toml index 1f00b2a..2ba8e3c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,12 +10,12 @@ repository = "https://forgejo.vanten-s.com/vanten-s/e2e-irc/" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -argparse = "0.2.2" -base64 = "0.21.4" -dirs = "5.0.1" +async-trait = "0.1.80" +clap = { version = "4.5.8", features = ["derive"] } eyre = "0.6.8" -ircparser-vanten = "0.2.1" -openssl = "0.10" +irc = { version = "1.0.0", default-features = false, features = ["tls-rust"] } pgp = "0.10.2" -rand = "0.8.5" -toml = "0.8.2" +rustls = "0.23.10" +tokio = { version = "1.38.0", features = ["full"] } +tokio-rustls = "0.26.0" +webpki-roots = "0.26.3" diff --git a/pk.asc b/pk.asc new file mode 100644 index 0000000..f1a8403 --- /dev/null +++ b/pk.asc @@ -0,0 +1,33 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: Keybase OpenPGP v2.1.15 +Comment: https://keybase.io/crypto + +xo0EZok7pwEEAM+XGXrB8gHwe6QGNF+LgoEHlezGHqNwM6pvqOG7zp18u+uKLZ4p +X9Ahbny9+BZe+xQrvq7sHeu0zl3qigalEBhaiLbUqwJr+gTl8ubjUcy0uVYLMKEj +1KkS7aJW66S+YBgWeD95z+GJUOlieA/I+KKG6pmD4OIo4wOAi5D7hEOLABEBAAHN +AMK0BBMBCgAeBQJmiTunAhsvAwsJBwMVCggCHgECF4ADFgIBAhkBAAoJEJE12r5B +NfECNQoEAIKfmGUmvaH4mK7gOqUUKzPfNzHR8BRPveuKmmyZFJIjihS2MqFWMsp3 +NDfWJ6P/MnFN2TW+0GYtAhLtT4mFVGm5MRJlRLStAWKAk5JB221JCWg5re/s7U4C +Ac6CFz/7eEM2x8LO/QkwAUi2YhaQNXxlA0OWqujw/wYUvqnO4abazo0EZok7pwEE +ANC6rVozjeDW0KT0odAukymQo3GGT/+TzrpZMfx6yHddZMoAxKZvbH7FwzuZI89i ++C3oKAajcAR0qxRuLhAvuZ+ULa1SSa0XRgAKpYl7O57B/etm1YLmgwJ0J/7xFkfW +kcj87TdWExT3XbzhWdbd4MuYTmYv1GLgWhuYHRj9RO3NABEBAAHCwIMEGAEKAA8F +AmaJO6cFCQ8JnAACGy4AqAkQkTXavkE18QKdIAQZAQoABgUCZok7pwAKCRCkd/34 +XN68WmdcA/9Ub7IajkAfLhHUyKxljXRoB9wTHmQ+nwFr27Du5MNvzJz+TAa6hjcb +gT2kNFNIFuURAVEbqcSKusVlBYU6wmXT4Oo3MeoOYhLdp3LCrt5Ide0FlGvKP34j +knh7kWKLBHigwCAcFd277EQEY2k/CO5Lcf9d1fQNb9BDVorAnzJb3ueBBACNyBAt +wladh66RhwmlIA81tX4P1eceExEmLMiheN2L3AhEA2EyqpVdkbVbbW8N+uFU6Yx0 +4Ucx0AqdhXAa4H2MSYBm0r2p4p3PVtIcMBhVxYt+jsS7geMQvDXPFUbYwWoSDut5 +Eq/yeqHoF9vN5NgmcIubREeTUroNjdbWYnT39s6NBGaJO6cBBACzfeLxDV1wmJ89 +yU/UWFGJeAqsCAW4tOtfA40h4Mli2GQu+VR3noBfe/szRjlGuVwjbyL2+VFqCcys +aPEpZ5MJWcdhrKuOIGGgGuJhe8MZfDXR/LIm+pOz6C577PQqYRnCKVlsdEd8gDij +gLrY5c/UmjRS2YYa4Znc5IPWSW5KVwARAQABwsCDBBgBCgAPBQJmiTunBQkDwmcA +AhsuAKgJEJE12r5BNfECnSAEGQEKAAYFAmaJO6cACgkQrPwDhFjiVufSKAP9HEHc +Y7gJ8u5H/su5enEeEIcr2K//SE2P8ihu56NZbXxbue8W1iEXqMu77bPr9tpJEHP6 +jqFliSaPDq8l7cFJg3nTs1Xv65REhrIUXDKXlxstvf9DTQPg7L6KbiDZ3McXuURw +wLRZto++8Piz9rvfm5RAD5Zzq/iFF0yqUyxhp45miQQAkBHakmIIi4JjG/VPZvNr +TSdkojREOzUPRykUjkCbTGuJ1094u+cWD/SYlCtpbCj3fqkPqZW0LcnyHrVHOsTE +M5vQT1btE0ohtxhQD/R65HFs+e4NjHKE6HpOCuzsMfh+nsyClSIurnLf/8Xisy5u +dNWmdpgEjpQ+kRaMlRf+/ik= +=glLb +-----END PGP PUBLIC KEY BLOCK----- diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..a5d13a3 --- /dev/null +++ b/shell.nix @@ -0,0 +1,18 @@ +{ pkgs ? import {} }: + +pkgs.mkShell rec { + buildInputs = with pkgs; [ + rustup + openssl + ]; + RUSTC_VERSION = "stable"; + LD_LIBRARY_PATH = with pkgs; lib.makeLibraryPath [ + openssl + ]; + shellHook = '' + rustup default $RUSTC_VERSION + rustup component add rust-analyzer + export PATH=$PATH:''${CARGO_HOME:-~/.cargo}/bin + export PATH=$PATH:''${RUSTUP_HOME:-~/.rustup}/toolchains/nightly-x86_64-unknown-linux-gnu/bin/ + ''; +} diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..4b7360e --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,60 @@ +use std::str::FromStr; + +use clap::Parser; + +#[derive(Parser, Debug, Hash, PartialEq, Eq, Clone)] +#[command(version, about)] +pub(super) struct Cli { + pub server: String, + + pub secret_key: String, + + #[arg(long, short)] + pub password: Option, + + #[arg(short, long, default_value_t = false)] + pub tls: bool, + + #[arg(short, long, default_value_t = EncryptionMethods::PGP)] + pub encryption_method: EncryptionMethods, +} + +#[derive(Debug, Hash, PartialEq, Eq, Clone)] +pub(crate) enum EncryptionMethods { + PGP, + None, +} + +#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] +pub struct EncryptionMethodParseError; + +impl std::fmt::Display for EncryptionMethods { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let name = match self { + Self::PGP => "pgp", + Self::None => "none", + }; + + f.write_str(name) + } +} + +impl FromStr for EncryptionMethods { + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "pgp" => Ok(Self::PGP), + "none" => Ok(Self::None), + _ => Err(EncryptionMethodParseError), + } + } + + type Err = EncryptionMethodParseError; +} + +impl std::fmt::Display for EncryptionMethodParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("Parsing Error") + } +} + +impl std::error::Error for EncryptionMethodParseError {} diff --git a/src/client_handler.rs b/src/client_handler.rs index 2776e42..4aec1c0 100644 --- a/src/client_handler.rs +++ b/src/client_handler.rs @@ -1,104 +1,131 @@ -use crate::helpers::bytes_to_privmsg_base64; -use crate::{encryption, helpers, State}; +use std::{io::ErrorKind, net::SocketAddr, sync::Arc}; + use eyre::Result; -use pgp::{Deserializable, SignedPublicKey}; -use std::collections::HashMap; -use std::sync::mpsc::{Receiver, Sender}; +use irc::proto::Message; +use rustls::pki_types::ServerName; +use tokio::{io::BufReader, net::TcpStream}; +use tokio_rustls::TlsConnector; -#[derive(Debug)] -struct InvalidCommand; +use crate::{ + cli::Cli, + encryption::Encryptor, + stream::{AsyncReader, AsyncWriter}, +}; -impl std::fmt::Display for InvalidCommand { - fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Ok(()) - } -} - -impl std::error::Error for InvalidCommand {} - -fn parse_bouncer_command(message: String, state: &mut State) -> Result<()> { - macro_rules! unwrap_option { - ($t:expr) => { - match $t { - Some(val) => val, - None => return Err(InvalidCommand.into()), - } - }; - } - - let mut splitted = message.split(' '); - match unwrap_option!(splitted.next()) { - "ALLOW_UNENCRYPTED" => state - .nicks_without_encryption - .push(unwrap_option!(splitted.next()).to_string().to_lowercase()), - _ => return Err(InvalidCommand.into()), - }; - Ok(()) -} - -pub fn handle_message_from_client( - recieved: &str, - public_key: &Vec, - server: &str, - keys: &mut HashMap, - writer_channel_tx: &Sender, - writer_channel_rx: &Receiver, - listener_channel_tx: &Sender, - _listener_channel_rx: &Receiver, - state: &mut State, +pub async fn handle( + encryptor: Encryptor, + reader: Box, + writer: Box, + addr: SocketAddr, + config: Cli, ) -> Result<()> { - let mut recieved = recieved.to_string(); + let client_reader = reader; + let client_writer = writer; - if recieved.split(' ').count() == 1 { - recieved += " "; - } + let server_reader: Box; + let server_writer: Box; - let parsed = ircparser::parse(&recieved); - let command = match parsed { - Ok(val) => val[0].clone(), - Err(_) => { - writer_channel_tx.send(recieved)?; - return Ok(()); - } - }; + if config.tls { + let root_store = + rustls::RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); - if command.command == "PRIVMSG" && !command.params[0].starts_with('#') { - if command.params[0] == "BOUNCER" { - return parse_bouncer_command(command.params[1].clone(), state); - } - if state - .nicks_without_encryption - .contains(&command.params[0].to_lowercase()) - { - writer_channel_tx.send(recieved)?; - return Ok(()); - } - let other = &command.params[0]; + let tls_config = rustls::ClientConfig::builder() + .with_root_certificates(root_store) + .with_no_client_auth(); - if !keys.contains_key(other) { - helpers::send_key(writer_channel_tx, other, public_key)?; - let key = helpers::recieve_message_base64( - writer_channel_rx, - listener_channel_tx, - "127.0.0.1", - server, - other, - "END_KEY", - )?; - let key = SignedPublicKey::from_bytes(key.as_slice())?; - keys.insert(other.to_string(), key); - } + let connector = TlsConnector::from(Arc::new(tls_config)); + let dnsname = ServerName::try_from(config.server.clone())?; - let foreign_key = keys.get(other).unwrap(); + let stream = TcpStream::connect((config.server.as_str(), 6697)).await?; + let stream = connector.connect(dnsname, stream).await?; - writer_channel_tx.send(format!("PRIVMSG {other} START_MESSAGE\r\n"))?; - writer_channel_tx.send(bytes_to_privmsg_base64( - &encryption::encrypt(foreign_key, &command.params[1])?, - other, - ))?; - writer_channel_tx.send(format!("PRIVMSG {other} END_MESSAGE\r\n"))?; + let server = stream; + + let (reader, writer) = tokio::io::split(server); + + server_reader = Box::new(BufReader::new(reader)); + server_writer = Box::new(writer); } else { - writer_channel_tx.send(recieved.replace("127.0.0.1", server))?; + let stream = TcpStream::connect((config.server.as_str(), 6667)).await?; + + let (reader, writer) = tokio::io::split(stream); + + server_reader = Box::new(BufReader::new(reader)); + server_writer = Box::new(writer); } + + tokio::spawn(async move { + let mut server = server_writer; + let mut client = client_reader; + loop { + let line = match client.read_line().await { + Ok(v) => v, + Err(e) => match e.kind() { + ErrorKind::BrokenPipe => break, + _ => { + println!("Couldn't read client: {e}"); + continue; + } + }, + }; + let parsed: Message = match line.parse() { + Ok(v) => v, + Err(e) => { + println!("Invalid command: {line}. Error: {e}"); + continue; + } + }; + + match parsed.command { + irc::proto::Command::PRIVMSG(target, string) => { + if !target.starts_with('#') { + if !ยง + println!("User wants to send a message to {target}. Message: {string}"); + } + } + _ => {} + }; + + match server.write_string(line).await { + Ok(_) => {} + Err(e) => match e.kind() { + ErrorKind::BrokenPipe => break, + _ => { + println!("Couldn't write server: {e}"); + continue; + } + }, + }; + } + println!("Client at {addr} disconnected"); + }); + + tokio::spawn(async move { + let mut server = server_reader; + let mut client = client_writer; + loop { + let line = match server.read_line().await { + Ok(v) => v, + Err(e) => match e.kind() { + ErrorKind::BrokenPipe => break, + _ => { + println!("Couldn't read server: {e}"); + continue; + } + }, + }; + match client.write_string(line).await { + Ok(_) => {} + Err(e) => match e.kind() { + ErrorKind::BrokenPipe => break, + _ => { + println!("Couldn't write client: {e}"); + continue; + } + }, + }; + } + }); + Ok(()) } diff --git a/src/encryption.rs b/src/encryption.rs deleted file mode 100644 index c94993d..0000000 --- a/src/encryption.rs +++ /dev/null @@ -1,29 +0,0 @@ -use eyre::Result; -use pgp::crypto::sym::SymmetricKeyAlgorithm; -use pgp::ser::Serialize; -use pgp::{Deserializable, Message, SignedPublicKey, SignedSecretKey}; -use rand::prelude::*; - -pub fn encrypt(key: &SignedPublicKey, message: &str) -> Result, pgp::errors::Error> { - let message = Message::new_literal("none", message); - let mut rng = StdRng::from_entropy(); - - let message = message.encrypt_to_keys(&mut rng, SymmetricKeyAlgorithm::AES128, &[key])?; - - message.to_bytes() -} - -pub fn decrypt<'a>( - key: &'a SignedSecretKey, - message: &[u8], - password: &'a str, -) -> Result { - let message = Message::from_bytes(message)?; // Convert message bytes to message object - let message = message.decrypt(|| password.to_string(), &[key])?.0; // Decrypt - let message = message.map(|x| x.unwrap()).collect::>(); // Get all messages - let message = &message[0]; // Get first message - let message = message.get_content()?.unwrap_or(Vec::new()); // Get message content as Vec - let message = String::from_utf8(message).unwrap(); // Convert to String - - Ok(message) -} diff --git a/src/encryption/mod.rs b/src/encryption/mod.rs new file mode 100644 index 0000000..2b616bd --- /dev/null +++ b/src/encryption/mod.rs @@ -0,0 +1,65 @@ +use eyre::Result; + +use crate::cli::EncryptionMethods; +use none::NoneEncryptor; +use pgp::PGPEncryptor; + +pub mod none; +pub mod pgp; + +pub trait EncryptionMethod: std::fmt::Debug + Send { + fn initialise(key: String, password: Option) -> Result + where + Self: Sized; + + fn encrypt(&self, message: String, name: String) -> Result; + fn decrypt(&self, message: String, name: String) -> Result; + + fn register_key(&mut self, name: String, key: String) -> Result<()>; + + fn public_key(&self) -> String; + + fn clone(&self) -> Box; +} + +#[derive(Debug)] +pub struct Encryptor { + encryption_method: Box, +} + +impl Encryptor { + pub fn initialise( + encryption_method: EncryptionMethods, + key: String, + password: Option, + ) -> Result { + use EncryptionMethods as EM; + + let encryption_method: Box = match encryption_method { + EM::PGP => Box::new(PGPEncryptor::initialise(key, password)?), + EM::None => Box::new(NoneEncryptor::initialise(key, password)?), + }; + + Ok(Encryptor { encryption_method }) + } + + pub fn register_key(&mut self, name: String, key: String) -> Result<()> { + self.encryption_method.register_key(name, key) + } + + pub fn encrypt(&self, message: String, name: String) -> Result { + self.encryption_method.encrypt(message, name) + } + + pub fn decrypt(&self, message: String, name: String) -> Result { + self.encryption_method.decrypt(message, name) + } +} + +impl Clone for Encryptor { + fn clone(&self) -> Self { + Encryptor { + encryption_method: self.encryption_method.clone(), + } + } +} diff --git a/src/encryption/none.rs b/src/encryption/none.rs new file mode 100644 index 0000000..7302585 --- /dev/null +++ b/src/encryption/none.rs @@ -0,0 +1,32 @@ +use eyre::Result; + +use super::EncryptionMethod; + +#[derive(Debug, Clone)] +pub struct NoneEncryptor; + +impl EncryptionMethod for NoneEncryptor { + fn encrypt(&self, message: String, _name: String) -> eyre::Result { + Ok(message) + } + + fn decrypt(&self, message: String, _name: String) -> eyre::Result { + Ok(message) + } + + fn public_key(&self) -> String { + String::new() + } + + fn initialise(_key: String, _password: Option) -> eyre::Result { + Ok(Self) + } + + fn register_key(&mut self, _name: String, _key: String) -> Result<()> { + Ok(()) + } + + fn clone(&self) -> Box { + Box::new(Clone::clone(self)) + } +} diff --git a/src/encryption/pgp.rs b/src/encryption/pgp.rs new file mode 100644 index 0000000..f508aa4 --- /dev/null +++ b/src/encryption/pgp.rs @@ -0,0 +1,76 @@ +use std::{ + collections::HashMap, + fs, + sync::{Arc, Mutex}, +}; + +use async_trait::async_trait; +use eyre::Result; +use irc::proto::{Command, Message}; +use pgp::{types::SecretKeyTrait, Deserializable}; +use tokio::sync::mpsc; + +use crate::stream::{AsyncReader, AsyncWriter}; + +use super::EncryptionMethod; + +#[derive(Debug, Clone)] +pub struct PGPEncryptor { + secret_key: pgp::SignedSecretKey, + public_key: String, + password: String, + keychain: Arc>>, +} + +impl EncryptionMethod for PGPEncryptor { + fn encrypt(&self, _message: String, _name: String) -> Result { + let _ = self.secret_key.clone(); + let _ = self.password.clone(); + todo!() + } + + fn decrypt(&self, _message: String, _name: String) -> Result { + todo!() + } + + fn initialise(key: String, password: Option) -> Result { + let password = password.unwrap_or("".to_string()); + + let secret_key = fs::read_to_string(&key)?; + let secret_key = pgp::SignedSecretKey::from_string(&secret_key)?.0; + let public_key = secret_key + .public_key() + .sign(&secret_key, || password.clone())? + .to_armored_string(None)?; + let keychain = Arc::new(Mutex::new(HashMap::new())); + + Ok(Self { + secret_key, + public_key, + password, + keychain, + }) + } + + fn public_key(&self) -> String { + self.public_key.clone() + } + + fn register_key(&mut self, name: String, key: String) -> Result<()> { + dbg!("Registering Key"); + dbg!("Key: {key}"); + let key = pgp::SignedPublicKey::from_string(&key)?.0; + dbg!("Parsed PK"); + + let mut keychain = self.keychain.lock().unwrap(); + dbg!("Got lock"); + keychain.insert(name, key); + dbg!("Inserted"); + + Ok(()) + } + + fn clone(&self) -> Box { + Box::new(Clone::clone(self)) + } +} diff --git a/src/helpers.rs b/src/helpers.rs deleted file mode 100644 index 7cb3b8e..0000000 --- a/src/helpers.rs +++ /dev/null @@ -1,144 +0,0 @@ -use base64::{engine::general_purpose, Engine as _}; -use eyre::Result; -use std::sync::mpsc::{self, Receiver, Sender}; - -#[derive(Debug)] -pub struct IrcParseError; - -impl std::fmt::Display for IrcParseError { - fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Ok(()) - } -} - -impl std::error::Error for IrcParseError {} - -static MAX_LENGTH: usize = 300; - -#[macro_export] -macro_rules! unwrap_or_return_result { - ($e:expr) => { - match $e { - Ok(val) => val, - Err(_) => return Ok(()), - } - }; -} - -#[macro_export] -macro_rules! unwrap_or_return_option { - ($e:expr) => { - match $e { - Some(val) => val, - None => return Ok(()), - } - }; -} - -pub struct State { - pub nicks_without_encryption: Vec, -} - -impl State { - pub fn new() -> Self { - State { - nicks_without_encryption: vec![ - "nickserv".to_string(), - "chanserv".to_string(), - "hostserv".to_string(), - ], - } - } -} - -fn forward( - message: String, - stream: &Sender, - server_local: &str, - server_forward: &str, -) -> Result<(), mpsc::SendError> { - match ircparser::parse(&message) { - Ok(val) => match val[0].command.as_str() { - "PRIVMSG" => stream.send(message), - _ => stream.send(message.replace(server_local, server_forward)), - }, - Err(_) => stream.send(message.replace(server_local, server_forward)), - } -} - -pub fn bytes_to_privmsg_base64(message: &Vec, reciever: &str) -> String { - let message_length = MAX_LENGTH - format!("PRIVMSG {reciever} :\r\n").len(); - let encoded = general_purpose::STANDARD.encode(message); - - let mut command = String::new(); - for line in encoded - .chars() - .collect::>() - .chunks(message_length) - .map(|c| c.iter().collect::()) - { - dbg!(&line); - command.push_str(&format!("PRIVMSG {reciever} :{line}\r\n")); - } - - println!("{}", encoded); - println!("{}", command); - command -} - -pub fn send_key(sender: &Sender, reciever: &str, key: &Vec) -> Result<()> { - sender.send(format!("PRIVMSG {reciever} :START_KEY\r\n"))?; - sender.send(bytes_to_privmsg_base64(key, reciever))?; - sender.send(format!("PRIVMSG {reciever} :END_KEY\r\n"))?; - - Ok(()) -} - -pub fn get_nick(userstring: &str) -> Option { - let userstring = userstring.chars().collect::>(); - let start_pos = userstring.iter().position(|&x| x == ':')? + 1; - let end_pos = userstring.iter().position(|&x| x == '!')?; - Some(userstring[start_pos..end_pos].iter().collect::()) -} - -pub fn recieve_message_base64( - writer_channel_rx: &Receiver, - forward_stream: &Sender, - server_local: &str, - server_forward: &str, - sender: &str, - end: &str, -) -> Result> { - let mut message: Vec = Vec::new(); - - while !message.contains(&end.to_string()) { - let recieved_raw = writer_channel_rx.recv()?; - - let parse_result = ircparser::parse(&recieved_raw); - - let recieved = match parse_result { - Ok(mut val) => val.pop_back().unwrap(), - Err(_) => return Err(IrcParseError.into()), - }; - - let begin_source_reciever = format!(":{sender}!"); - if recieved.command != "PRIVMSG" - || !recieved - .source - .clone() - .unwrap_or("".to_string()) - .starts_with(&begin_source_reciever) - || recieved.params[0].starts_with('#') - { - forward(recieved_raw, forward_stream, server_local, server_forward)?; - continue; - } - - message.push(recieved.params[1].clone()); - } - message.pop(); - - let foreign_key = dbg!(message.concat()); - let foreign_key = general_purpose::STANDARD.decode(foreign_key)?; - Ok(foreign_key) -} diff --git a/src/listener_server.rs b/src/listener_server.rs deleted file mode 100644 index a4c7c6d..0000000 --- a/src/listener_server.rs +++ /dev/null @@ -1,59 +0,0 @@ -use std::io::{ErrorKind, Read, Write}; -use std::net::{TcpListener, TcpStream}; -use std::sync::mpsc::{self, TryRecvError}; -use std::thread; -use std::time::Duration; - -fn stream_handler(tx: &mpsc::Sender, rx: &mpsc::Receiver, mut stream: TcpStream) { - loop { - let mut buffer: Vec = Vec::new(); - let mut buf: [u8; 1] = [0]; - let newline: u8 = b'\n'; - - while buf[0] != newline { - match stream.read(&mut buf) { - Ok(_length) => buffer.push(buf[0]), - Err(_error) => match _error.kind() { - ErrorKind::WouldBlock => {} - _ => { - dbg!(_error); - return; - } - }, - } - match rx.try_recv() { - Ok(value) => { - match stream.write_all(value.as_bytes()) { - Ok(_) => {} - Err(_e) => { - dbg!(_e); - return; - } - }; - } - Err(TryRecvError::Empty) => {} - Err(TryRecvError::Disconnected) => return, - } - thread::sleep(Duration::from_micros(100)); - } - - let _ = tx.send(dbg!(String::from_utf8_lossy(&buffer).to_string())); - } -} - -pub fn listen_to_client(tx: mpsc::Sender, rx: mpsc::Receiver, port: String) { - let listener = TcpListener::bind("127.0.0.1:".to_string() + &port) - .expect(&("Couldn't start listener on 127.0.0.1 port ".to_string() + &port)); - loop { - let (stream, ip) = listener.accept().unwrap(); - println!("Got connection from {ip}"); - - stream - .set_nonblocking(true) - .expect("Couldn't set nonblocking"); - - stream_handler(&tx, &rx, stream); - println!("Closed connection with {ip}"); - let _ = tx.send("DUMMY CLOSE_CONNECTION".to_string()); - } -} diff --git a/src/main.rs b/src/main.rs index 9471546..b0cd65a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,150 +1,36 @@ -use argparse::{ArgumentParser, Store}; -use dirs::config_local_dir; +use clap::Parser; +use encryption::Encryptor; use eyre::Result; -use helpers::State; -use pgp::{Deserializable, SignedPublicKey, SignedSecretKey}; -use std::collections::HashMap; -use std::fs; -use std::sync::mpsc; -use std::thread; -use std::time::Duration; -use toml::Table; +use tokio::io::BufReader; +mod cli; mod client_handler; mod encryption; -mod helpers; -mod listener_server; -mod server_handler; -mod writer_client; +mod stream; -fn main() -> Result<()> { - let config_file = config_local_dir() - .expect("Couldn't get config directory") - .join("e2e-irc/config.toml"); +#[tokio::main] +async fn main() -> Result<()> { + let config = cli::Cli::parse(); - if !config_file.exists() { - panic!("Create a config file at {}", config_file.display()); - } + let listener = tokio::net::TcpListener::bind("127.0.0.1:6666").await?; - let parsed_config = String::from_utf8_lossy(&fs::read(config_file)?).parse::()?; - - let public_key_location = parsed_config - .get("public_key") - .expect("Coudln't get public_key. Try creating it in the config") - .as_str() - .expect("Couldn't convert public_key to str"); - let secret_key_location = parsed_config - .get("secret_key") - .expect("Coudln't get secret_key. Try creating it in the config") - .as_str() - .expect("Couldn't convert secret_key to str"); - - let default_password = toml::Value::String(String::new()); - - let passwd = parsed_config - .get("passwd") - .unwrap_or(&default_password) - .as_str() - .expect("Coudln't convert passwd to str"); - - let mut server = "irc.vanten-s.com".to_string(); - let mut port = "6666".to_string(); - let mut server_port = "6697".to_string(); - - { - let mut ap = ArgumentParser::new(); - ap.set_description("Encrypted IRC Bouncer"); - ap.refer(&mut server) - .add_argument( - "server", - Store, - "The Address Of The Server The Bouncer Connects To", - ) - .required(); - ap.refer(&mut port) - .add_option(&["-p", "--port"], Store, "The Port The Bouncer Binds To"); - ap.refer(&mut server_port).add_option( - &["--sp", "--server-port"], - Store, - "The TLS Enabled Port Of The Server", - ); - ap.parse_args_or_exit(); - } - - let public_key = fs::read(public_key_location)?; - let secret_key = SignedSecretKey::from_bytes(fs::read(secret_key_location)?.as_slice())?; - - let (listener_channel_send_tx, listener_channel_rx) = mpsc::channel(); - let (listener_channel_tx, listener_channel_recv_rx) = mpsc::channel(); - - let (writer_channel_tx, writer_channel_send_rx) = mpsc::channel(); - let (writer_channel_recv_tx, writer_channel_rx) = mpsc::channel(); - - let tmp_port = port.clone(); - thread::spawn(move || { - listener_server::listen_to_client( - listener_channel_send_tx, - listener_channel_recv_rx, - tmp_port, - ) - }); - let tmp_port = server_port.clone(); - let tmp_server = server.clone(); - thread::spawn(move || { - writer_client::write_to_server( - &tmp_server, - &tmp_port, - writer_channel_send_rx, - writer_channel_recv_tx, - ) - }); - - let mut keys: HashMap = HashMap::new(); - let mut state = State::new(); + let encryptor = Encryptor::initialise( + config.encryption_method.clone(), + config.secret_key.clone(), + config.password.clone(), + )?; loop { - match listener_channel_rx.try_recv() { - Ok(message) => { - let _ = client_handler::handle_message_from_client( - &message, - &public_key, - &server, - &mut keys, - &writer_channel_tx, - &writer_channel_rx, - &listener_channel_tx, - &listener_channel_rx, - &mut state, - ); - } - Err(error) => match error { - mpsc::TryRecvError::Empty => {} - mpsc::TryRecvError::Disconnected => panic!("listener_channel_rx disconnected"), - }, - }; + let (socket, address) = listener.accept().await?; - match writer_channel_rx.try_recv() { - Ok(message) => { - let _ = server_handler::handle_message_from_server( - &message, - &public_key, - &secret_key, - &server, - passwd, - &mut keys, - &writer_channel_tx, - &writer_channel_rx, - &listener_channel_tx, - &listener_channel_rx, - &state, - ); - } - Err(error) => match error { - mpsc::TryRecvError::Empty => {} - mpsc::TryRecvError::Disconnected => panic!("writer_channel_rx disconnected"), - }, - }; + let (reader, writer) = tokio::io::split(socket); - thread::sleep(Duration::from_millis(1)); + tokio::spawn(client_handler::handle( + encryptor.clone(), + Box::new(BufReader::new(reader)), + Box::new(writer), + address, + config.clone(), + )); } } diff --git a/src/server_handler.rs b/src/server_handler.rs deleted file mode 100644 index da7c391..0000000 --- a/src/server_handler.rs +++ /dev/null @@ -1,87 +0,0 @@ -use crate::unwrap_or_return_option; -use crate::unwrap_or_return_result; -use crate::State; -use crate::{encryption, helpers}; -use eyre::Result; -use pgp::{Deserializable, SignedPublicKey, SignedSecretKey}; -use std::collections::HashMap; -use std::sync::mpsc::{self, Receiver, Sender}; - -fn forward( - message: &str, - listener_channel_tx: &Sender, - server: &str, -) -> Result<(), mpsc::SendError> { - listener_channel_tx.send(message.replace(server, "127.0.0.1")) -} - -pub fn handle_message_from_server( - recieved: &str, - public_key: &Vec, - secret_key: &SignedSecretKey, - server: &str, - passwd: &str, - keys: &mut HashMap, - writer_channel_tx: &Sender, - writer_channel_rx: &Receiver, - listener_channel_tx: &Sender, - _listener_channel_rx: &Receiver, - state: &State, -) -> Result<()> { - let recieved_parsed = &unwrap_or_return_result!(ircparser::parse(recieved))[0]; - - let default_reciever = String::new(); - - let reciever = match recieved_parsed.params.get(0) { - Some(val) => val, - None => &default_reciever, - }; - - if recieved_parsed.command != "PRIVMSG" - || reciever.starts_with('#') - || state.nicks_without_encryption.contains(reciever) - { - forward(recieved, listener_channel_tx, server)?; - return Ok(()); - } - - dbg!(&recieved_parsed); - - let source = unwrap_or_return_option!(&recieved_parsed.source); - - if recieved_parsed.params[1] == "START_MESSAGE" { - let sender = unwrap_or_return_option!(helpers::get_nick(source)); - - let message = helpers::recieve_message_base64( - writer_channel_rx, - listener_channel_tx, - server, - "127.0.0.1", - &sender, - "END_MESSAGE", - )?; // Get - let message = encryption::decrypt(secret_key, &message, passwd)?; // Decrypt - - listener_channel_tx.send(recieved.replace("START_MESSAGE", &message))?; // Send - } else if recieved_parsed.params[1] == "START_KEY" { - let sender = unwrap_or_return_option!(helpers::get_nick(source)); - let to_send = helpers::bytes_to_privmsg_base64(public_key, &sender); - writer_channel_tx.send(to_send)?; - writer_channel_tx.send(format!("PRIVMSG {sender} END_KEY\r\n"))?; - - let foreign_key = helpers::recieve_message_base64( - writer_channel_rx, - listener_channel_tx, - server, - "127.0.0.1", - &sender, - "END_KEY", - )?; - dbg!(&foreign_key); - let key = SignedPublicKey::from_bytes(foreign_key.as_slice())?; - println!("Got a key from {sender}"); - keys.insert(sender.to_string(), key); - } - - Ok(()) -} diff --git a/src/stream.rs b/src/stream.rs new file mode 100644 index 0000000..7ff12fe --- /dev/null +++ b/src/stream.rs @@ -0,0 +1,40 @@ +use std::io::{Error, ErrorKind}; + +use async_trait::async_trait; +use eyre::Result; +use tokio::io::{AsyncBufRead, AsyncBufReadExt, AsyncWriteExt}; + +#[async_trait] +pub trait AsyncReader: Unpin + Send { + async fn read_line(&mut self) -> Result; +} + +#[async_trait] +pub trait AsyncWriter: Unpin + Send { + async fn write_string(&mut self, buf: String) -> Result<(), Error>; +} + +#[async_trait] +impl AsyncReader for T { + async fn read_line(&mut self) -> Result { + let mut buf = Vec::new(); + let length = AsyncBufReadExt::read_until(self, b'\n', &mut buf).await?; + + if length == 0 { + return Err(Error::new(ErrorKind::BrokenPipe, "Server Disconnected")); + } + + let string = String::from_utf8_lossy(&buf).to_string(); + + Ok(string) + } +} + +#[async_trait] +impl AsyncWriter for T { + async fn write_string(&mut self, buf: String) -> Result<(), Error> { + self.write_all_buf(&mut buf.as_bytes()).await?; + + Ok(()) + } +} diff --git a/src/writer_client.rs b/src/writer_client.rs deleted file mode 100644 index c38218d..0000000 --- a/src/writer_client.rs +++ /dev/null @@ -1,73 +0,0 @@ -use openssl::ssl::{SslConnector, SslMethod}; -use std::io::{ErrorKind, Write}; -use std::net::TcpStream; -use std::sync::mpsc; -use std::thread; -use std::time::Duration; - -pub fn write_to_server( - server: &str, - port: &str, - rx: mpsc::Receiver, - tx: mpsc::Sender, -) { - 'big: loop { - println!("Connecting to {server}:{port}"); - let tcp_stream = - TcpStream::connect(format!("{server}:{port}")).expect("Couldn't connect to server"); - - let connector = SslConnector::builder(SslMethod::tls()).unwrap().build(); - let mut stream = connector - .connect(server, &tcp_stream) - .expect("Couldn't start TLS"); - - stream - .get_mut() - .set_nonblocking(true) - .expect("Failed to set nonblocking"); - - loop { - let mut buffer: Vec = Vec::new(); - let mut buf: [u8; 1] = [0]; - let newline: u8 = b'\n'; - - while buf[0] != newline { - match stream.ssl_read(&mut buf) { - Ok(_length) => { - if _length > 0 { - buffer.push(buf[0]); - } - } - Err(_error) => match _error.io_error() { - None => { - dbg!(_error.ssl_error()); - continue 'big; - } - Some(error) => match error.kind() { - ErrorKind::WouldBlock => {} - _ => { - dbg!(error.kind()); - println!("Couldn't read the stream"); - continue 'big; - } - }, - }, - } - let value = rx.try_recv().unwrap_or("".to_string()); - match value.as_str() { - "DUMMY CLOSE_CONNECTION" => { - continue 'big; - } - _ => {} - } - match stream.write_all(value.as_bytes()) { - Ok(_) => {} - Err(_e) => println!("Couldn't send {value}"), - }; - thread::sleep(Duration::from_micros(100)); - } - - let _ = tx.send(dbg!(String::from_utf8_lossy(&buffer).to_string())); - } - } -} diff --git a/test.asc b/test.asc new file mode 100644 index 0000000..9c64dac --- /dev/null +++ b/test.asc @@ -0,0 +1,54 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: Keybase OpenPGP v2.1.15 +Comment: https://keybase.io/crypto + +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 +lRf+/ik= +=4/Rx +-----END PGP PRIVATE KEY BLOCK-----