Add configuration file and update main to load server settings
This commit is contained in:
parent
07b9969416
commit
6761e43cae
5 changed files with 267 additions and 132 deletions
76
Cargo.lock
generated
76
Cargo.lock
generated
|
@ -172,7 +172,9 @@ dependencies = [
|
|||
"log",
|
||||
"rand",
|
||||
"rand_core",
|
||||
"serde",
|
||||
"tokio",
|
||||
"toml",
|
||||
"x25519-dalek",
|
||||
]
|
||||
|
||||
|
@ -300,6 +302,12 @@ dependencies = [
|
|||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "fiat-crypto"
|
||||
version = "0.2.9"
|
||||
|
@ -343,6 +351,12 @@ version = "0.31.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.9"
|
||||
|
@ -355,6 +369,16 @@ version = "2.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inout"
|
||||
version = "0.1.3"
|
||||
|
@ -623,6 +647,15 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.2"
|
||||
|
@ -694,6 +727,40 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.17.0"
|
||||
|
@ -882,6 +949,15 @@ version = "0.52.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.6.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x25519-dalek"
|
||||
version = "2.0.1"
|
||||
|
|
|
@ -13,3 +13,5 @@ rand = "0.8.5"
|
|||
rand_core = "0.6.4"
|
||||
crypto = "0.5.1"
|
||||
base64 = "0.21"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
toml = "0.8.19"
|
2
config.toml
Normal file
2
config.toml
Normal file
|
@ -0,0 +1,2 @@
|
|||
address = "127.0.0.1"
|
||||
port = "25565"
|
157
src/client/mod.rs
Normal file
157
src/client/mod.rs
Normal file
|
@ -0,0 +1,157 @@
|
|||
pub(crate) mod handlers {
|
||||
use aes_gcm::{
|
||||
aead::{Aead, KeyInit, OsRng},
|
||||
Aes256Gcm, Key, Nonce,
|
||||
};
|
||||
|
||||
use base64::{engine::general_purpose::STANDARD as BASE64, Engine as _};
|
||||
use log::{debug, error, info};
|
||||
use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::sync::broadcast;
|
||||
use x25519_dalek::{EphemeralSecret, PublicKey};
|
||||
|
||||
pub async fn handle_client(
|
||||
socket: TcpStream,
|
||||
tx: broadcast::Sender<String>,
|
||||
mut rx: broadcast::Receiver<String>,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let (reader, mut writer) = socket.into_split();
|
||||
let mut reader = BufReader::new(reader);
|
||||
let mut line = String::new();
|
||||
|
||||
let server_secret = EphemeralSecret::random_from_rng(OsRng);
|
||||
let server_public = PublicKey::from(&server_secret);
|
||||
|
||||
// Send the server's public key to the client
|
||||
writer.write_all(server_public.as_bytes()).await?;
|
||||
|
||||
// Receive the client's public key
|
||||
let mut client_public_bytes = [0u8; 32];
|
||||
reader.read_exact(&mut client_public_bytes).await?;
|
||||
let client_public = PublicKey::from(client_public_bytes);
|
||||
|
||||
// Compute the shared secret
|
||||
let shared_secret = server_secret.diffie_hellman(&client_public);
|
||||
|
||||
let key = Key::<Aes256Gcm>::from_slice(shared_secret.as_bytes());
|
||||
|
||||
let cipher_reader = Aes256Gcm::new(&key);
|
||||
let cipher_writer = Aes256Gcm::new(&key);
|
||||
let nonce_reader = Nonce::from_slice(b"unique nonce"); // 96-bits; fixed nonce
|
||||
let nonce_writer = nonce_reader.clone();
|
||||
|
||||
debug!("Reciving Username");
|
||||
|
||||
// Read the username from the client
|
||||
line.clear();
|
||||
reader.read_line(&mut line).await?;
|
||||
let decoded = BASE64.decode(line.trim().as_bytes())?;
|
||||
let decrypted = cipher_reader
|
||||
.decrypt(&nonce_reader, decoded.as_ref())
|
||||
.unwrap();
|
||||
let username = String::from_utf8(decrypted)?;
|
||||
let username_read = username.clone(); // Clone for read task
|
||||
let username_write = username.clone(); // Clone for write task
|
||||
|
||||
// Read task for receiving messages from the client
|
||||
let read_task = tokio::spawn(async move {
|
||||
loop {
|
||||
line.clear();
|
||||
match reader.read_line(&mut line).await {
|
||||
Ok(bytes_read) => {
|
||||
if bytes_read == 0 {
|
||||
info!("Client disconnected");
|
||||
break;
|
||||
}
|
||||
|
||||
let decoded = match BASE64.decode(line.trim().as_bytes()) {
|
||||
Ok(decoded) => decoded,
|
||||
Err(e) => {
|
||||
error!("Base64 decode error: {:?}", e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let decrypted = match cipher_reader.decrypt(&nonce_reader, decoded.as_ref())
|
||||
{
|
||||
Ok(decrypted) => decrypted,
|
||||
Err(e) => {
|
||||
error!("Decryption error: {:?}", e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let message = match String::from_utf8(decrypted) {
|
||||
Ok(msg) => msg,
|
||||
Err(e) => {
|
||||
error!("UTF-8 conversion error: {:?}", e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
info!(
|
||||
"Received message from {}: {}",
|
||||
username_read,
|
||||
message.trim()
|
||||
);
|
||||
|
||||
if message.trim() == "/quit" {
|
||||
info!("Client requested to quit");
|
||||
break;
|
||||
}
|
||||
|
||||
let formatted_message = format!("{}: {}", username_read, message.trim());
|
||||
|
||||
// Broadcast the message to all clients
|
||||
match tx.send(formatted_message) {
|
||||
Ok(_) => info!("Message broadcast successfully"),
|
||||
Err(e) => {
|
||||
error!("Failed to broadcast message: {:?}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Error reading from client: {:?}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Write task for sending messages to the client
|
||||
let write_task = tokio::spawn(async move {
|
||||
while let Ok(msg) = rx.recv().await {
|
||||
if !msg.is_empty() {
|
||||
// Encrypt the message with error handling
|
||||
let encrypted = match cipher_writer.encrypt(&nonce_writer, msg.as_bytes()) {
|
||||
Ok(encrypted) => encrypted,
|
||||
Err(e) => {
|
||||
error!("Encryption error: {:?}", e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
// Base64 encode and format with newline
|
||||
let message = format!("{}\n", BASE64.encode(&encrypted));
|
||||
|
||||
// Write with proper error handling
|
||||
if let Err(e) = writer.write_all(message.as_bytes()).await {
|
||||
error!("Failed to send message: {:?}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Wait for both tasks to complete
|
||||
tokio::select! {
|
||||
_ = read_task => (),
|
||||
_ = write_task => (),
|
||||
}
|
||||
|
||||
info!("Client handling completed");
|
||||
Ok(())
|
||||
}
|
||||
}
|
162
src/main.rs
162
src/main.rs
|
@ -1,22 +1,43 @@
|
|||
use aes_gcm::{
|
||||
aead::{Aead, KeyInit, OsRng},
|
||||
Aes256Gcm, Key, Nonce,
|
||||
};
|
||||
use base64::{engine::general_purpose::STANDARD as BASE64, Engine as _};
|
||||
mod client;
|
||||
use client::handlers::handle_client;
|
||||
|
||||
use colog;
|
||||
use log::{error, info};
|
||||
use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use serde::Deserialize;
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::sync::broadcast;
|
||||
use x25519_dalek::{EphemeralSecret, PublicKey};
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct Config {
|
||||
address: String,
|
||||
port: String,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// Initialize the logger
|
||||
colog::init();
|
||||
|
||||
// Load the configuration from config file
|
||||
let config = match std::fs::read_to_string("config.toml") {
|
||||
Ok(config) => match toml::from_str::<Config>(&config) {
|
||||
Ok(config) => config,
|
||||
Err(e) => {
|
||||
error!("Failed to parse config file: {:?}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Failed to read config file: {:?}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
info!("Configuration loaded: {:?}", config);
|
||||
// Bind a TCP listener to accept incoming connections
|
||||
let listener = TcpListener::bind("0.0.0.0:8080").await.unwrap();
|
||||
let listener = TcpListener::bind(config.address + ":" + config.port.as_str())
|
||||
.await
|
||||
.unwrap();
|
||||
info!("Server running on port 8080");
|
||||
|
||||
// Create a broadcast channel for sharing messages
|
||||
|
@ -38,126 +59,3 @@ async fn main() {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_client(
|
||||
socket: TcpStream,
|
||||
tx: broadcast::Sender<String>,
|
||||
mut rx: broadcast::Receiver<String>,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let (reader, mut writer) = socket.into_split();
|
||||
let mut reader = BufReader::new(reader);
|
||||
let mut line = String::new();
|
||||
|
||||
let server_secret = EphemeralSecret::random_from_rng(OsRng);
|
||||
let server_public = PublicKey::from(&server_secret);
|
||||
|
||||
// Send the server's public key to the client
|
||||
writer.write_all(server_public.as_bytes()).await?;
|
||||
|
||||
// Receive the client's public key
|
||||
let mut client_public_bytes = [0u8; 32];
|
||||
reader.read_exact(&mut client_public_bytes).await?;
|
||||
let client_public = PublicKey::from(client_public_bytes);
|
||||
|
||||
// Compute the shared secret
|
||||
let shared_secret = server_secret.diffie_hellman(&client_public);
|
||||
|
||||
let key = Key::<Aes256Gcm>::from_slice(shared_secret.as_bytes());
|
||||
|
||||
let cipher_reader = Aes256Gcm::new(&key);
|
||||
let cipher_writer = Aes256Gcm::new(&key);
|
||||
let nonce_reader = Nonce::from_slice(b"unique nonce"); // 96-bits; fixed nonce
|
||||
let nonce_writer = nonce_reader.clone();
|
||||
|
||||
// Read task for receiving messages from the client
|
||||
let read_task = tokio::spawn(async move {
|
||||
loop {
|
||||
line.clear();
|
||||
match reader.read_line(&mut line).await {
|
||||
Ok(bytes_read) => {
|
||||
if bytes_read == 0 {
|
||||
info!("Client disconnected");
|
||||
break;
|
||||
}
|
||||
|
||||
let decoded = match BASE64.decode(line.trim().as_bytes()) {
|
||||
Ok(decoded) => decoded,
|
||||
Err(e) => {
|
||||
error!("Base64 decode error: {:?}", e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let decrypted = match cipher_reader.decrypt(&nonce_reader, decoded.as_ref()) {
|
||||
Ok(decrypted) => decrypted,
|
||||
Err(e) => {
|
||||
error!("Decryption error: {:?}", e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let message = match String::from_utf8(decrypted) {
|
||||
Ok(msg) => msg,
|
||||
Err(e) => {
|
||||
error!("UTF-8 conversion error: {:?}", e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
info!("Received message: {}", message.trim());
|
||||
|
||||
if message.trim() == "/quit" {
|
||||
info!("Client requested to quit");
|
||||
break;
|
||||
}
|
||||
|
||||
// Broadcast the message to all clients
|
||||
match tx.send(message) {
|
||||
Ok(_) => info!("Message broadcast successfully"),
|
||||
Err(e) => {
|
||||
error!("Failed to broadcast message: {:?}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Error reading from client: {:?}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Write task for sending messages to the client
|
||||
let write_task = tokio::spawn(async move {
|
||||
while let Ok(msg) = rx.recv().await {
|
||||
if !msg.is_empty() {
|
||||
// Encrypt the message
|
||||
let encrypted = match cipher_writer.encrypt(&nonce_writer, msg.as_bytes()) {
|
||||
Ok(encrypted) => encrypted,
|
||||
Err(e) => {
|
||||
error!("Encryption error: {:?}", e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
// Base64 encode the encrypted message
|
||||
let encoded = BASE64.encode(&encrypted);
|
||||
|
||||
if let Err(e) = writer.write_all((encoded + "\n").as_bytes()).await {
|
||||
error!("Failed to send message: {:?}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Wait for both tasks to complete
|
||||
tokio::select! {
|
||||
_ = read_task => (),
|
||||
_ = write_task => (),
|
||||
}
|
||||
|
||||
info!("Client handling completed");
|
||||
Ok(())
|
||||
}
|
Loading…
Add table
Reference in a new issue