better error handling

This commit is contained in:
2025-04-13 06:24:48 +02:00
parent 53d0baa4a7
commit 792ee3a708
4 changed files with 160 additions and 109 deletions
+59 -44
View File
@@ -10,52 +10,55 @@ use openssl::{
symm::{decrypt, encrypt, Cipher}, symm::{decrypt, encrypt, Cipher},
}; };
use crate::response::Error;
fn aes_encrypt( fn aes_encrypt(
key: &[u8], key: &[u8],
// iv: &[u8],
plaintext: &[u8], plaintext: &[u8],
) -> Result<Vec<u8>, Box<dyn std::error::Error>> { ) -> Result<Vec<u8>, Error> {
let cipher = Cipher::aes_256_ecb(); let cipher = Cipher::aes_256_ecb();
let ciphertext = encrypt(cipher, key, None, plaintext)?; encrypt(cipher, key, None, plaintext)
Ok(ciphertext) .map_err(|e| Error::EncryptionError(format!("Failed to encrypt: {}", e)))
} }
fn aes_decrypt( fn aes_decrypt(
key: &[u8], key: &[u8],
// iv: &[u8],
ciphertext: &[u8], ciphertext: &[u8],
) -> Result<Vec<u8>, Box<dyn std::error::Error>> { ) -> Result<Vec<u8>, Error> {
let cipher = Cipher::aes_256_ecb(); let cipher = Cipher::aes_256_ecb();
let plaintext = decrypt(cipher, key, None, ciphertext)?; decrypt(cipher, key, None, ciphertext)
Ok(plaintext) .map_err(|e| Error::EncryptionError(format!("Failed to decrypt: {}", e)))
} }
#[allow(deprecated)] #[allow(deprecated)]
pub fn handshake(mut stream: TcpStream, rsa: Rsa<Private>) -> ETcp { pub fn handshake(mut stream: TcpStream, rsa: Rsa<Private>) -> Result<ETcp, Error> {
let mut reader = BufReader::new(stream.try_clone().unwrap()); let stream_clone = stream.try_clone()
.map_err(|e| Error::NetworkError(format!("Failed to clone stream: {}", e)))?;
let mut reader = BufReader::new(stream_clone);
stream.write_all(&rsa.public_key_to_pem().unwrap()).unwrap(); let public_key = rsa.public_key_to_pem()
.map_err(|e| Error::EncryptionError(format!("Failed to get public key: {}", e)))?;
stream.write_all(&public_key)
.map_err(|e| Error::NetworkError(format!("Failed to write public key: {}", e)))?;
stream.flush().unwrap(); stream.flush()
.map_err(|e| Error::NetworkError(format!("Failed to flush stream: {}", e)))?;
let mut aes_encoded = String::new(); let mut aes_encoded = String::new();
reader.read_line(&mut aes_encoded)
.map_err(|e| Error::NetworkError(format!("Failed to read AES key: {}", e)))?;
reader.read_line(&mut aes_encoded).unwrap(); let encrypted_data = base64::decode(aes_encoded.trim())
.map_err(|e| Error::EncryptionError(format!("Failed to decode base64: {}", e)))?;
let encrypted_data = base64::decode(aes_encoded.trim()).expect("Failed to decode base64");
let mut aes_decrypted = vec![0; 256]; let mut aes_decrypted = vec![0; 256];
let aes_len = rsa.private_decrypt(
let aes_len = rsa
.private_decrypt(
&encrypted_data, &encrypted_data,
&mut aes_decrypted, &mut aes_decrypted,
openssl::rsa::Padding::PKCS1, openssl::rsa::Padding::PKCS1,
) ).map_err(|e| Error::EncryptionError(format!("Failed to decrypt AES key: {}", e)))?;
.expect("Decryption failed");
println!("len: {}", aes_len); println!("len: {}", aes_len);
aes_decrypted.truncate(aes_len); aes_decrypted.truncate(aes_len);
println!( println!(
@@ -64,11 +67,11 @@ pub fn handshake(mut stream: TcpStream, rsa: Rsa<Private>) -> ETcp {
aes_decrypted aes_decrypted
); );
ETcp { Ok(ETcp {
stream, stream,
aes: aes_decrypted, aes: aes_decrypted,
reader, reader,
} })
} }
pub struct ETcp { pub struct ETcp {
@@ -79,39 +82,51 @@ pub struct ETcp {
impl ETcp { impl ETcp {
#[allow(deprecated)] #[allow(deprecated)]
pub fn send<T: Display>(&mut self, d: T) { pub fn send<T: Display>(&mut self, d: T) -> Result<(), Error> {
let a = aes_encrypt(&self.aes, d.to_string().as_bytes()).unwrap(); let encrypted = aes_encrypt(&self.aes, d.to_string().as_bytes())?;
let encoded = base64::encode(encrypted) + "\n";
self.stream self.stream.write_all(encoded.as_bytes())
.write_all((base64::encode(a) + "\n").as_bytes()) .map_err(|e| Error::NetworkError(format!("Failed to write to stream: {}", e)))?;
.unwrap();
self.stream.flush().unwrap(); self.stream.flush()
.map_err(|e| Error::NetworkError(format!("Failed to flush stream: {}", e)))
} }
#[allow(deprecated)] #[allow(deprecated)]
pub fn read(&mut self) -> Option<String> { pub fn read(&mut self) -> Result<Option<String>, Error> {
let mut v_encoded = String::new(); let mut v_encoded = String::new();
self.reader.read_line(&mut v_encoded).unwrap(); if self.reader.read_line(&mut v_encoded)
.map_err(|e| Error::NetworkError(format!("Failed to read line: {}", e)))? == 0 {
let encrypted_data = base64::decode(v_encoded.trim()).expect("Failed to decode base64"); return Ok(None);
Some(String::from_utf8(aes_decrypt(&self.aes, &encrypted_data).unwrap()).unwrap())
} }
pub fn close(&self) { let encrypted_data = base64::decode(v_encoded.trim())
self.stream .map_err(|e| Error::EncryptionError(format!("Failed to decode base64: {}", e)))?;
.shutdown(std::net::Shutdown::Both)
.expect("shutdown call failed"); let decrypted = aes_decrypt(&self.aes, &encrypted_data)?;
let text = String::from_utf8(decrypted)
.map_err(|e| Error::EncryptionError(format!("Failed to decode UTF-8: {}", e)))?;
Ok(Some(text))
} }
pub fn clone(&mut self) -> Self { pub fn close(&self) -> Result<(), Error> {
let stream = self.stream.try_clone().unwrap(); self.stream.shutdown(std::net::Shutdown::Both)
ETcp { .map_err(|e| Error::NetworkError(format!("Failed to shutdown stream: {}", e)))
reader: BufReader::new(stream.try_clone().unwrap()), }
pub fn clone(&mut self) -> Result<Self, Error> {
let stream = self.stream.try_clone()
.map_err(|e| Error::NetworkError(format!("Failed to clone stream: {}", e)))?;
let stream_clone = stream.try_clone()
.map_err(|e| Error::NetworkError(format!("Failed to clone stream: {}", e)))?;
Ok(ETcp {
reader: BufReader::new(stream_clone),
stream, stream,
aes: self.aes.clone(), aes: self.aes.clone(),
} })
} }
} }
+42 -23
View File
@@ -1,10 +1,10 @@
use std::{ use std::{
net::TcpListener, net::{TcpListener, TcpStream},
sync::Arc, sync::Arc,
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use openssl::rsa::Rsa; use openssl::{rsa::Rsa, pkey::Private};
pub mod database; pub mod database;
pub mod encryption; pub mod encryption;
@@ -12,27 +12,49 @@ pub mod methods;
pub mod parser; pub mod parser;
pub mod response; pub mod response;
fn main() -> std::io::Result<()> { fn main() -> Result<(), response::Error> {
let rsa = Rsa::generate(2048).expect("Failed to generate RSA keys"); let rsa = Rsa::generate(2048)
.map_err(|e| response::Error::EncryptionError(format!("Failed to generate RSA keys: {}", e)))?;
let client = mongodb::sync::Client::with_uri_str("mongodb://admin:admin@localhost/").unwrap(); let client = mongodb::sync::Client::with_uri_str("mongodb://admin:admin@localhost/")
.map_err(|e| response::Error::DatabaseError(format!("Failed to connect to MongoDB: {}", e)))?;
let database = Arc::new(database::Database::new(&client)); let database = Arc::new(database::Database::new(&client));
let listener = TcpListener::bind("0.0.0.0:8080").unwrap(); let listener = TcpListener::bind("0.0.0.0:8080")
.map_err(|e| response::Error::NetworkError(format!("Failed to bind to port 8080: {}", e)))?;
println!("Server listening on port 8080");
for stream_result in listener.incoming() {
let stream = match stream_result {
Ok(stream) => stream,
Err(e) => {
eprintln!("Failed to accept connection: {}", e);
continue;
}
};
for stream in listener.incoming() {
println!("New connection"); println!("New connection");
let database = Arc::clone(&database); let database = Arc::clone(&database);
let rsa = rsa.clone(); let rsa = rsa.clone();
std::thread::spawn({ std::thread::spawn(move || {
move || { if let Err(e) = handle_client(stream, rsa, database) {
let mut stream = encryption::handshake(stream.unwrap(), rsa); eprintln!("Client error: {}", e);
}
});
}
Ok(())
}
fn handle_client(stream: TcpStream, rsa: Rsa<Private>, database: Arc<database::Database>) -> Result<(), response::Error> {
let mut stream = encryption::handshake(stream, rsa)?;
let mut last_activity = Instant::now(); let mut last_activity = Instant::now();
match methods::Session::new(stream.clone(), database) { match methods::Session::new(stream.clone()?, database) {
Ok((session, res)) => { Ok((session, res)) => {
stream.send(res); stream.send(res)?;
loop { loop {
// Check for inactivity // Check for inactivity
@@ -41,41 +63,38 @@ fn main() -> std::io::Result<()> {
"[MOJANG] {} inactive for too long", "[MOJANG] {} inactive for too long",
session.local_player.name session.local_player.name
); );
methods::player::logout(&session).unwrap(); methods::player::logout(&session)?;
println!("[MOJANG] {} went offline", session.local_player.name); println!("[MOJANG] {} went offline", session.local_player.name);
break; break;
} }
match stream.read() { match stream.read()? {
None => { None => {
// Client disconnected // Client disconnected
println!("[MOJANG] {} disconnected", session.local_player.name); println!("[MOJANG] {} disconnected", session.local_player.name);
methods::player::logout(&session).unwrap(); methods::player::logout(&session)?;
println!("[MOJANG] {} went offline", session.local_player.name); println!("[MOJANG] {} went offline", session.local_player.name);
break; break;
} }
Some(request_string) => { Some(request_string) => {
last_activity = Instant::now(); last_activity = Instant::now();
let (method, params) = parser::parse(&request_string).unwrap(); let (method, params) = parser::parse(&request_string)?;
let response = session.handle_request(&method, &params); let response = session.handle_request(&method, &params);
stream.send(match response { stream.send(match response {
Ok(response) => response.to_string(), Ok(response) => response.to_string(),
Err(e) => format!("!{e}"), Err(e) => format!("!{e}"),
}); })?;
}
} }
};
} }
} }
Err(e) => { Err(e) => {
stream.send(format!("!{e}")); stream.send(format!("!{e}"))?;
println!("Disconnected"); println!("Disconnected");
stream.close(); stream.close()?;
} }
} }
}
});
}
Ok(()) Ok(())
} }
+24 -9
View File
@@ -36,8 +36,10 @@ impl Session {
mpsc::channel::<std::result::Result<String, crate::response::Error>>(); mpsc::channel::<std::result::Result<String, crate::response::Error>>();
std::thread::spawn(move || match stream.read() { std::thread::spawn(move || match stream.read() {
Some(session_token) => token_send.send(Ok(session_token)), Ok(Some(session_token)) => token_send.send(Ok(session_token)),
_ => token_send.send(Err(crate::response::Error::InvalidHandshake("Failed to read session token".to_string()))), _ => token_send.send(Err(crate::response::Error::InvalidHandshake(
"Failed to read session token".to_string(),
))),
}); });
match token_recv.recv_timeout(std::time::Duration::from_secs(20)) { match token_recv.recv_timeout(std::time::Duration::from_secs(20)) {
@@ -46,17 +48,26 @@ impl Session {
let response = minreq::get("https://api.minecraftservices.com/minecraft/profile") let response = minreq::get("https://api.minecraftservices.com/minecraft/profile")
.with_header("Authorization", &format!("Bearer {session_token}")) .with_header("Authorization", &format!("Bearer {session_token}"))
.send() .send()
.map_err(|_| crate::response::Error::InvalidSession("Failed to validate session".to_string()))?; .map_err(|_| {
crate::response::Error::InvalidSession(
"Failed to validate session".to_string(),
)
})?;
// If the session is invalid, return an error // If the session is invalid, return an error
if response.status_code != 200 { if response.status_code != 200 {
return Err(crate::response::Error::InvalidSession(format!("Invalid session status code: {}", response.status_code))); return Err(crate::response::Error::InvalidSession(format!(
"Invalid session status code: {}",
response.status_code
)));
} }
// Parse the player data // Parse the player data
let local_player: LocalPlayer = response let local_player: LocalPlayer = response.json().map_err(|_| {
.json() crate::response::Error::InvalidSession(
.map_err(|_| crate::response::Error::InvalidSession("Failed to parse player data".to_string()))?; "Failed to parse player data".to_string(),
)
})?;
println!("[MOJANG] {} successfully logged on", &local_player.name); println!("[MOJANG] {} successfully logged on", &local_player.name);
@@ -72,8 +83,12 @@ impl Session {
Err(e) => Err(e), Err(e) => Err(e),
} }
} }
Err(mpsc::RecvTimeoutError::Timeout) => Err(crate::response::Error::Timeout("Session handshake timed out".to_string())), Err(mpsc::RecvTimeoutError::Timeout) => Err(crate::response::Error::Timeout(
_ => Err(crate::response::Error::InvalidHandshake("Failed to receive handshake response".to_string())), "Session handshake timed out".to_string(),
)),
_ => Err(crate::response::Error::InvalidHandshake(
"Failed to receive handshake response".to_string(),
)),
} }
} }
+2
View File
@@ -73,6 +73,7 @@ pub enum Error {
Timeout(String), Timeout(String),
AuthenticationError(String), AuthenticationError(String),
ValidationError(String), ValidationError(String),
EncryptionError(String),
} }
impl std::fmt::Display for Error { impl std::fmt::Display for Error {
@@ -89,6 +90,7 @@ impl std::fmt::Display for Error {
Error::Timeout(operation) => write!(f, "Operation timed out: {}", operation), Error::Timeout(operation) => write!(f, "Operation timed out: {}", operation),
Error::AuthenticationError(details) => write!(f, "Authentication failed: {}", details), Error::AuthenticationError(details) => write!(f, "Authentication failed: {}", details),
Error::ValidationError(details) => write!(f, "Validation failed: {}", details), Error::ValidationError(details) => write!(f, "Validation failed: {}", details),
Error::EncryptionError(details) => write!(f, "Encryption error: {}", details),
} }
} }
} }