Compare commits

...

26 commits

Author SHA1 Message Date
vanten-s e4fd3224cf Merge pull request 'Changed docs' (#8) from dev-laptop into main
Reviewed-on: #8
2023-10-21 12:07:05 +02:00
vanten-s 5c24ae5f96
Changed docs 2023-10-21 12:06:28 +02:00
vanten-s c895210cea Merge pull request 'Got ready for publication' (#7) from dev-laptop into main
Reviewed-on: #7
2023-10-21 12:05:05 +02:00
vanten-s 4aed403c97
Got ready for publication 2023-10-21 12:04:50 +02:00
vanten-s 247f35994b Merge pull request 'Version 3.0.0' (#6) from dev-laptop into main
Reviewed-on: #6
2023-10-21 11:46:12 +02:00
vanten-s 6eeeb4a2e8
Version 3.0.0 2023-10-21 11:10:44 +02:00
vanten-s 2783cc9ca8 Merge pull request 'Added an internal bot that can remove encryption for specific nicks' (#5) from dev-laptop into main
Reviewed-on: #5
2023-10-21 11:07:36 +02:00
vanten-s bdb14a5b6d
Added an internal bot that can remove encryption for specific nicks 2023-10-21 11:05:17 +02:00
vanten-s 805caebe35 Merge pull request 'Version 2' (#4) from dev-laptop into main
Reviewed-on: #4
2023-10-19 22:14:20 +02:00
vanten-s b9af753fc7
Version 2 2023-10-19 22:08:13 +02:00
vanten-s e26db6d68b
Merge branch 'dev-laptop' 2023-10-19 22:04:46 +02:00
vanten-s 7c5bc94ade
You can now reconnect to the server 2023-10-19 22:03:18 +02:00
vanten-s d38da05d05
Fixed docs, version number and made server required 2023-10-19 17:23:38 +02:00
vanten-s 83b10eb3d5
Changed from a .env to config file 2023-10-19 17:08:05 +02:00
vanten-s b480ce09e5
Added cargo.lock to gitignore 2023-10-16 11:29:08 +02:00
vanten-s e19e3b0d25 Merge pull request '.env, docs, performance, project name' (#2) from dev-laptop into main
Reviewed-on: #2
2023-10-15 13:29:23 +02:00
vanten-s d185ceb7c0
Updated docs 2023-10-15 13:23:59 +02:00
vanten-s 0a7b931bb9
Changed project name 2023-10-15 13:23:40 +02:00
vanten-s cb87ebe318
Added better error handling and .env file 2023-10-15 13:13:07 +02:00
vanten-s 620ad761fd
Prepared .env 2023-10-13 14:44:55 +02:00
vanten-s ca79a1e513
Basic README 2023-10-12 22:13:24 +02:00
vanten-s e83cf5bfb5
Merged 2023-10-12 22:07:00 +02:00
vanten-s 72945e8b94
Major performace increase 2023-10-12 22:04:50 +02:00
vanten-s 6626110758
Major performace increase 2023-10-12 22:01:17 +02:00
vanten-s 34b2e0e5a9
Minor formatting changes 2023-10-12 21:26:52 +02:00
vanten-s 25b8f5b51e
New MAJOR version 2023-10-12 13:59:07 +02:00
11 changed files with 606 additions and 1683 deletions

3
.gitignore vendored
View file

@ -1,3 +1,2 @@
/target /target
secret.gpg Cargo.lock
public.gpg

1382
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,13 +1,21 @@
[package] [package]
name = "irc-e2e" name = "e2e-irc"
version = "0.1.0" version = "3.0.0"
edition = "2021" edition = "2021"
license = "GPL-3.0"
keywords = ["irc", "encryption"]
description = "An IRC bouncer that can send encrypted messages"
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 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
argparse = "0.2.2"
base64 = "0.21.4" base64 = "0.21.4"
ircparser = "0.2.1" dirs = "5.0.1"
eyre = "0.6.8"
ircparser-vanten = "0.2.1"
openssl = "0.10" openssl = "0.10"
pgp = "0.10.2" pgp = "0.10.2"
rand = "0.8.5" rand = "0.8.5"
toml = "0.8.2"

35
README.md Normal file
View file

@ -0,0 +1,35 @@
# e2e-irc
This is an IRC bouncer that supports end-to-end encryption and is horribly written.
# Configuration
e2e-irc uses a config file in `~/.config/e2e-irc/config.toml` on linux.
The configuration file is a toml file with two or three fields:
- public_key the location of the public pgp key
- secret_key the location of the secret pgp key
- passwd the password of the pgp key
# Usage
```
e2e-irc [OPTIONS] SERVER
Encrypted IRC Bouncer
Positional arguments:
server The Address Of The Server The Bouncer Connects To
Optional arguments:
-h,--help Show this help message and exit
-p,--port PORT The Port The Bouncer Binds To
--sp,--server-port SERVER_PORT
The TLS Enabled Port Of The Server
```
# Install
```bash
cargo install e2e-irc
```
# Run
```bash
e2e-irc [OPTIONS] SERVER
```

104
src/client_handler.rs Normal file
View file

@ -0,0 +1,104 @@
use crate::helpers::bytes_to_privmsg_base64;
use crate::{encryption, helpers, State};
use eyre::Result;
use pgp::{Deserializable, SignedPublicKey};
use std::collections::HashMap;
use std::sync::mpsc::{Receiver, Sender};
#[derive(Debug)]
struct InvalidCommand;
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<u8>,
server: &str,
keys: &mut HashMap<String, SignedPublicKey>,
writer_channel_tx: &Sender<String>,
writer_channel_rx: &Receiver<String>,
listener_channel_tx: &Sender<String>,
_listener_channel_rx: &Receiver<String>,
state: &mut State,
) -> Result<()> {
let mut recieved = recieved.to_string();
if recieved.split(' ').count() == 1 {
recieved += " ";
}
let parsed = ircparser::parse(&recieved);
let command = match parsed {
Ok(val) => val[0].clone(),
Err(_) => {
writer_channel_tx.send(recieved)?;
return Ok(());
}
};
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];
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 foreign_key = keys.get(other).unwrap();
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"))?;
} else {
writer_channel_tx.send(recieved.replace("127.0.0.1", server))?;
}
Ok(())
}

29
src/encryption.rs Normal file
View file

@ -0,0 +1,29 @@
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<Vec<u8>, 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<String, pgp::errors::Error> {
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::<Vec<Message>>(); // Get all messages
let message = &message[0]; // Get first message
let message = message.get_content()?.unwrap_or(Vec::new()); // Get message content as Vec<u8>
let message = String::from_utf8(message).unwrap(); // Convert to String
Ok(message)
}

144
src/helpers.rs Normal file
View file

@ -0,0 +1,144 @@
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<String>,
}
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<String>,
server_local: &str,
server_forward: &str,
) -> Result<(), mpsc::SendError<String>> {
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<u8>, 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::<Vec<char>>()
.chunks(message_length)
.map(|c| c.iter().collect::<String>())
{
dbg!(&line);
command.push_str(&format!("PRIVMSG {reciever} :{line}\r\n"));
}
println!("{}", encoded);
println!("{}", command);
command
}
pub fn send_key(sender: &Sender<String>, reciever: &str, key: &Vec<u8>) -> 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<String> {
let userstring = userstring.chars().collect::<Vec<char>>();
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::<String>())
}
pub fn recieve_message_base64(
writer_channel_rx: &Receiver<String>,
forward_stream: &Sender<String>,
server_local: &str,
server_forward: &str,
sender: &str,
end: &str,
) -> Result<Vec<u8>> {
let mut message: Vec<String> = 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)
}

View file

@ -1,15 +1,10 @@
use std::io::{ErrorKind, Read, Write}; use std::io::{ErrorKind, Read, Write};
use std::net::TcpListener; use std::net::{TcpListener, TcpStream};
use std::sync::mpsc; use std::sync::mpsc::{self, TryRecvError};
use std::thread;
pub fn listen_to_client(tx: mpsc::Sender<String>, rx: mpsc::Receiver<String>, port: &str) { use std::time::Duration;
let listener = TcpListener::bind("127.0.0.1:".to_string() + port).unwrap();
let mut stream = listener.accept().unwrap().0;
stream
.set_nonblocking(true)
.expect("Couldn't set nonblocking");
fn stream_handler(tx: &mpsc::Sender<String>, rx: &mpsc::Receiver<String>, mut stream: TcpStream) {
loop { loop {
let mut buffer: Vec<u8> = Vec::new(); let mut buffer: Vec<u8> = Vec::new();
let mut buf: [u8; 1] = [0]; let mut buf: [u8; 1] = [0];
@ -19,9 +14,10 @@ pub fn listen_to_client(tx: mpsc::Sender<String>, rx: mpsc::Receiver<String>, po
match stream.read(&mut buf) { match stream.read(&mut buf) {
Ok(_length) => buffer.push(buf[0]), Ok(_length) => buffer.push(buf[0]),
Err(_error) => match _error.kind() { Err(_error) => match _error.kind() {
ErrorKind::WouldBlock => {}, ErrorKind::WouldBlock => {}
_ => { _ => {
dbg!(_error); dbg!(_error);
return;
} }
}, },
} }
@ -29,13 +25,35 @@ pub fn listen_to_client(tx: mpsc::Sender<String>, rx: mpsc::Receiver<String>, po
Ok(value) => { Ok(value) => {
match stream.write_all(value.as_bytes()) { match stream.write_all(value.as_bytes()) {
Ok(_) => {} Ok(_) => {}
Err(_e) => println!("Couldn't send {value}"), Err(_e) => {
dbg!(_e);
return;
}
}; };
} }
Err(_e) => {} Err(TryRecvError::Empty) => {}
Err(TryRecvError::Disconnected) => return,
} }
thread::sleep(Duration::from_micros(100));
} }
let _ = tx.send(String::from_utf8_lossy(&buffer).to_string()); let _ = tx.send(dbg!(String::from_utf8_lossy(&buffer).to_string()));
}
}
pub fn listen_to_client(tx: mpsc::Sender<String>, rx: mpsc::Receiver<String>, 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());
} }
} }

View file

@ -1,110 +1,78 @@
use base64::{engine::general_purpose, Engine as _}; use argparse::{ArgumentParser, Store};
use ircparser; use dirs::config_local_dir;
use pgp::crypto::sym::SymmetricKeyAlgorithm; use eyre::Result;
use pgp::ser::Serialize; use helpers::State;
use pgp::{Deserializable, Message, SignedPublicKey, SignedSecretKey}; use pgp::{Deserializable, SignedPublicKey, SignedSecretKey};
use rand::prelude::*;
use std::collections::HashMap; use std::collections::HashMap;
use std::net::{Shutdown, TcpStream}; use std::fs;
use std::sync::mpsc; use std::sync::mpsc;
use std::thread; use std::thread;
use std::time::Duration; use std::time::Duration;
use std::{env, fs}; use toml::Table;
mod client_handler;
mod encryption;
mod helpers;
mod listener_server; mod listener_server;
mod server_handler;
mod writer_client; mod writer_client;
macro_rules! try_recv { fn main() -> Result<()> {
($rx:ident) => { let config_file = config_local_dir()
match $rx.try_recv() { .expect("Couldn't get config directory")
Ok(recieved) => recieved, .join("e2e-irc/config.toml");
Err(_e) => match _e {
mpsc::TryRecvError::Empty => String::new(),
mpsc::TryRecvError::Disconnected => {
panic!("Error!")
}
},
}
};
}
fn bytes_to_privmsg_base64(message: Vec<u8>, reciever: &str) -> String { if !config_file.exists() {
let mut command = String::new(); panic!("Create a config file at {}", config_file.display());
for line in general_purpose::STANDARD
.encode(message)
.chars()
.collect::<Vec<char>>()
.chunks(500)
.map(|c| c.iter().collect::<String>())
{
command.push_str(&format!("PRIVMSG {reciever} :{line}\r\n"));
} }
println!("{}", command); let parsed_config = String::from_utf8_lossy(&fs::read(config_file)?).parse::<Table>()?;
command
}
fn encrypt(key: &SignedPublicKey, message: &str) -> Result<Vec<u8>, pgp::errors::Error> { let public_key_location = parsed_config
let message = Message::new_literal("none", message); .get("public_key")
let mut rng = StdRng::from_entropy(); .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 message = message.encrypt_to_keys(&mut rng, SymmetricKeyAlgorithm::AES128, &[key])?; let default_password = toml::Value::String(String::new());
Ok(message.to_bytes()?) let passwd = parsed_config
} .get("passwd")
.unwrap_or(&default_password)
.as_str()
.expect("Coudln't convert passwd to str");
fn decrypt<'a>( let mut server = "irc.vanten-s.com".to_string();
key: &'a SignedSecretKey, let mut port = "6666".to_string();
message: Vec<u8>, let mut server_port = "6697".to_string();
password: &'a str,
) -> Result<String, pgp::errors::Error> {
let message = Message::from_bytes(message.as_slice())?; // Convert message bytes to message object
let message = message.decrypt(|| password.to_string(), &[key])?.0; // Decrypt
let message = message.map(|x| x.unwrap()).collect::<Vec<Message>>(); // Get all messages
let message = &message[0]; // Get first message
let message = message.get_content()?.unwrap_or(Vec::new()); // Get message content as Vec<u8>
let message = String::from_utf8(message).unwrap(); // Convert to String
Ok(message) {
} 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();
}
fn get_nick(userstring: &str) -> String { let public_key = fs::read(public_key_location)?;
let userstring = userstring.chars().collect::<Vec<char>>(); let secret_key = SignedSecretKey::from_bytes(fs::read(secret_key_location)?.as_slice())?;
let start_pos = userstring.iter().position(|&x| x == ':').unwrap() + 1;
let end_pos = userstring.iter().position(|&x| x == '!').unwrap();
userstring[start_pos..end_pos].iter().collect::<String>()
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut args = env::args().collect::<Vec<String>>();
let server = args[1].clone();
let port = args[2].clone();
let default_password = String::new();
let passwd = args.pop().unwrap_or(default_password);
let stream = TcpStream::connect(format!("{server}:6697"))?;
let public_key = fs::read("public.gpg")?;
let secret_key = SignedSecretKey::from_bytes(fs::read("secret.gpg")?.as_slice())?;
let reader_stream = match stream.try_clone() {
Ok(stream) => stream,
Err(_error) => {
let _ = stream.shutdown(Shutdown::Both);
panic!("Failed to create the reader stream")
}
};
let writer_stream = match stream.try_clone() {
Ok(stream) => stream,
Err(_error) => {
let _ = stream.shutdown(Shutdown::Both);
let _ = reader_stream.shutdown(Shutdown::Both);
panic!("Failed to create the writer stream")
}
};
let (listener_channel_send_tx, listener_channel_rx) = mpsc::channel(); let (listener_channel_send_tx, listener_channel_rx) = mpsc::channel();
let (listener_channel_tx, listener_channel_recv_rx) = mpsc::channel(); let (listener_channel_tx, listener_channel_recv_rx) = mpsc::channel();
@ -112,175 +80,71 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let (writer_channel_tx, writer_channel_send_rx) = mpsc::channel(); let (writer_channel_tx, writer_channel_send_rx) = mpsc::channel();
let (writer_channel_recv_tx, writer_channel_rx) = mpsc::channel(); let (writer_channel_recv_tx, writer_channel_rx) = mpsc::channel();
let tmp_port = port.clone();
thread::spawn(move || { thread::spawn(move || {
listener_server::listen_to_client(listener_channel_send_tx, listener_channel_recv_rx, &port) 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(); let tmp_server = server.clone();
thread::spawn(|| { thread::spawn(move || {
writer_client::write_to_server( writer_client::write_to_server(
writer_stream, &tmp_server,
tmp_server, &tmp_port,
writer_channel_send_rx, writer_channel_send_rx,
writer_channel_recv_tx, writer_channel_recv_tx,
) )
}); });
let mut keys: HashMap<String, SignedPublicKey> = HashMap::new(); let mut keys: HashMap<String, SignedPublicKey> = HashMap::new();
let mut userstring = String::new(); let mut state = State::new();
loop { loop {
let recieved = try_recv!(listener_channel_rx); match listener_channel_rx.try_recv() {
Ok(message) => {
if !recieved.is_empty() { let _ = client_handler::handle_message_from_client(
let command = &ircparser::parse(&recieved).expect("Got an invalid IRC instruction")[0]; &message,
&public_key,
if command.command == "PRIVMSG" && !command.params[0].starts_with("#") { &server,
let reciever = &command.params[0]; &mut keys,
if !keys.contains_key(reciever) { &writer_channel_tx,
writer_channel_tx.send(format!("PRIVMSG {reciever} :START_KEY\r\n"))?; &writer_channel_rx,
&listener_channel_tx,
writer_channel_tx &listener_channel_rx,
.send(bytes_to_privmsg_base64(public_key.clone(), reciever))?; &mut state,
writer_channel_tx.send(format!("PRIVMSG {reciever} :END_KEY\r\n"))?; );
let mut foreign_key: Vec<String> = Vec::new();
while !foreign_key.contains(&"END_KEY".to_string()) {
let recieved_raw = writer_channel_rx.recv()?;
let recieved = &ircparser::parse(&recieved_raw).unwrap()[0];
let begin_source_reciever = format!(":{reciever}!");
if recieved.command != "PRIVMSG"
|| !recieved
.source
.clone()
.unwrap_or("".to_string())
.starts_with(&begin_source_reciever)
{
listener_channel_tx.send(recieved_raw)?;
continue;
}
foreign_key.push(recieved.params[1].clone());
}
foreign_key.pop();
println!("Got a foreign key from {reciever}");
let foreign_key = foreign_key.concat();
dbg!(&foreign_key);
let foreign_key = general_purpose::STANDARD.decode(foreign_key)?;
dbg!("It's decoded");
let key = SignedPublicKey::from_bytes(foreign_key.as_slice())?;
dbg!("And I now got it's SignedPublicKey format");
keys.insert(reciever.to_string(), key);
}
let foreign_key = keys.get(reciever).unwrap();
writer_channel_tx.send(format!("PRIVMSG {reciever} START_MESSAGE\r\n"))?;
writer_channel_tx.send(bytes_to_privmsg_base64(
encrypt(&foreign_key, &command.params[1])?,
reciever,
))?;
writer_channel_tx.send(format!("PRIVMSG {reciever} END_MESSAGE\r\n"))?;
} else {
writer_channel_tx.send(recieved.replace("127.0.0.1", &server))?;
} }
} Err(error) => match error {
mpsc::TryRecvError::Empty => {}
mpsc::TryRecvError::Disconnected => panic!("listener_channel_rx disconnected"),
},
};
let recieved = try_recv!(writer_channel_rx); match writer_channel_rx.try_recv() {
if !recieved.is_empty() { Ok(message) => {
let recieved_parsed = &ircparser::parse(&recieved).unwrap()[0]; let _ = server_handler::handle_message_from_server(
&message,
if recieved_parsed.command != "PRIVMSG" &public_key,
|| recieved_parsed &secret_key,
.params &server,
.get(0) passwd,
.unwrap_or(&String::new()) &mut keys,
.starts_with("#") &writer_channel_tx,
{ &writer_channel_rx,
listener_channel_tx.send(recieved.replace(&server, "127.0.0.1"))?; &listener_channel_tx,
continue; &listener_channel_rx,
&state,
);
} }
Err(error) => match error {
dbg!(&recieved_parsed); mpsc::TryRecvError::Empty => {}
mpsc::TryRecvError::Disconnected => panic!("writer_channel_rx disconnected"),
},
};
if recieved_parsed.params[1] == "START_MESSAGE" { thread::sleep(Duration::from_millis(1));
let reciever = get_nick(&recieved_parsed.source.clone().unwrap());
let sample_privmsg = recieved.clone();
let mut encrypted_message: Vec<String> = Vec::new();
while !encrypted_message.contains(&"END_MESSAGE".to_string()) {
let recieved_raw = writer_channel_rx.recv()?;
let recieved = &ircparser::parse(&recieved_raw).unwrap()[0];
let begin_source_reciever = format!(":{reciever}!");
if recieved.command != "PRIVMSG"
|| !recieved
.source
.clone()
.unwrap_or("".to_string())
.starts_with(&begin_source_reciever)
{
listener_channel_tx.send(recieved_raw.replace(&server, "127.0.0.1"))?;
continue;
}
encrypted_message.push(recieved.params[1].clone());
}
encrypted_message.pop();
let encrypted_message = encrypted_message.join("");
let encrypted_message = general_purpose::STANDARD.decode(encrypted_message)?;
let decrypted_message = decrypt(&secret_key, encrypted_message, &passwd)?;
listener_channel_tx
.send(sample_privmsg.replace("START_MESSAGE", &decrypted_message))?;
} else if recieved_parsed.params[1] == "START_KEY" {
let reciever = get_nick(&recieved_parsed.source.clone().unwrap());
let to_send = bytes_to_privmsg_base64(public_key.clone(), &reciever);
writer_channel_tx.send(to_send)?;
writer_channel_tx.send(format!("PRIVMSG {reciever} END_KEY\r\n"))?;
dbg!("Started Getting a key");
let reciever = get_nick(&recieved_parsed.source.clone().unwrap());
let mut foreign_key: Vec<String> = Vec::new();
while !foreign_key.contains(&"END_KEY".to_string()) {
let recieved_raw = writer_channel_rx.recv()?;
let recieved = &ircparser::parse(&recieved_raw).unwrap()[0];
let begin_source_reciever = format!(":{reciever}!");
if recieved.command != "PRIVMSG"
|| !recieved
.source
.clone()
.unwrap_or("".to_string())
.starts_with(&begin_source_reciever)
{
listener_channel_tx.send(recieved_raw.replace(&server, "127.0.0.1"))?;
continue;
}
foreign_key.push(recieved.params[1].clone());
}
foreign_key.pop();
dbg!("Got a foreign key from");
dbg!(&reciever);
let foreign_key = foreign_key.concat();
let foreign_key = general_purpose::STANDARD.decode(foreign_key)?;
dbg!("Decoded the key");
let key = SignedPublicKey::from_bytes(foreign_key.as_slice())?;
dbg!("Deserialized the key");
keys.insert(reciever.to_string(), key);
}
}
} }
} }

87
src/server_handler.rs Normal file
View file

@ -0,0 +1,87 @@
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<String>,
server: &str,
) -> Result<(), mpsc::SendError<String>> {
listener_channel_tx.send(message.replace(server, "127.0.0.1"))
}
pub fn handle_message_from_server(
recieved: &str,
public_key: &Vec<u8>,
secret_key: &SignedSecretKey,
server: &str,
passwd: &str,
keys: &mut HashMap<String, SignedPublicKey>,
writer_channel_tx: &Sender<String>,
writer_channel_rx: &Receiver<String>,
listener_channel_tx: &Sender<String>,
_listener_channel_rx: &Receiver<String>,
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(())
}

View file

@ -2,55 +2,72 @@ use openssl::ssl::{SslConnector, SslMethod};
use std::io::{ErrorKind, Write}; use std::io::{ErrorKind, Write};
use std::net::TcpStream; use std::net::TcpStream;
use std::sync::mpsc; use std::sync::mpsc;
use std::thread;
use std::time::Duration;
pub fn write_to_server( pub fn write_to_server(
tcp_stream: TcpStream, server: &str,
server: String, port: &str,
rx: mpsc::Receiver<String>, rx: mpsc::Receiver<String>,
tx: mpsc::Sender<String>, tx: mpsc::Sender<String>,
) { ) {
let connector = SslConnector::builder(SslMethod::tls()).unwrap().build(); 'big: loop {
let mut stream = connector println!("Connecting to {server}:{port}");
.connect(&server, tcp_stream) let tcp_stream =
.expect("Couldn't start TLS"); TcpStream::connect(format!("{server}:{port}")).expect("Couldn't connect to server");
stream let connector = SslConnector::builder(SslMethod::tls()).unwrap().build();
.get_mut() let mut stream = connector
.set_nonblocking(true) .connect(server, &tcp_stream)
.expect("Failed to set nonblocking"); .expect("Couldn't start TLS");
loop { stream
let mut buffer: Vec<u8> = Vec::new(); .get_mut()
let mut buf: [u8; 1] = [0]; .set_nonblocking(true)
let newline: u8 = b'\n'; .expect("Failed to set nonblocking");
while buf[0] != newline { loop {
match stream.ssl_read(&mut buf) { let mut buffer: Vec<u8> = Vec::new();
Ok(_length) => { let mut buf: [u8; 1] = [0];
if _length > 0 { let newline: u8 = b'\n';
buffer.push(buf[0]);
} while buf[0] != newline {
} match stream.ssl_read(&mut buf) {
Err(_error) => match _error.io_error() { Ok(_length) => {
None => { if _length > 0 {
dbg!(_error); buffer.push(buf[0]);
}
Some(error) => match error.kind() {
ErrorKind::WouldBlock => {}
_ => {
dbg!(error.kind());
println!("Couldn't read the stream");
} }
}
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 value = rx.try_recv().unwrap_or("".to_string());
match stream.write_all(value.as_bytes()) {
Ok(_) => {}
Err(_e) => println!("Couldn't send {value}"),
};
}
let _ = tx.send(dbg!(String::from_utf8_lossy(&buffer).to_string())); let _ = tx.send(dbg!(String::from_utf8_lossy(&buffer).to_string()));
}
} }
} }