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",
|
"log",
|
||||||
"rand",
|
"rand",
|
||||||
"rand_core",
|
"rand_core",
|
||||||
|
"serde",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"toml",
|
||||||
"x25519-dalek",
|
"x25519-dalek",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -300,6 +302,12 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "equivalent"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fiat-crypto"
|
name = "fiat-crypto"
|
||||||
version = "0.2.9"
|
version = "0.2.9"
|
||||||
|
@ -343,6 +351,12 @@ version = "0.31.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.15.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "hermit-abi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
|
@ -355,6 +369,16 @@ version = "2.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
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]]
|
[[package]]
|
||||||
name = "inout"
|
name = "inout"
|
||||||
version = "0.1.3"
|
version = "0.1.3"
|
||||||
|
@ -623,6 +647,15 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_spanned"
|
||||||
|
version = "0.6.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "signal-hook-registry"
|
name = "signal-hook-registry"
|
||||||
version = "1.4.2"
|
version = "1.4.2"
|
||||||
|
@ -694,6 +727,40 @@ dependencies = [
|
||||||
"syn",
|
"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]]
|
[[package]]
|
||||||
name = "typenum"
|
name = "typenum"
|
||||||
version = "1.17.0"
|
version = "1.17.0"
|
||||||
|
@ -882,6 +949,15 @@ version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winnow"
|
||||||
|
version = "0.6.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "x25519-dalek"
|
name = "x25519-dalek"
|
||||||
version = "2.0.1"
|
version = "2.0.1"
|
||||||
|
|
|
@ -13,3 +13,5 @@ rand = "0.8.5"
|
||||||
rand_core = "0.6.4"
|
rand_core = "0.6.4"
|
||||||
crypto = "0.5.1"
|
crypto = "0.5.1"
|
||||||
base64 = "0.21"
|
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::{
|
mod client;
|
||||||
aead::{Aead, KeyInit, OsRng},
|
use client::handlers::handle_client;
|
||||||
Aes256Gcm, Key, Nonce,
|
|
||||||
};
|
|
||||||
use base64::{engine::general_purpose::STANDARD as BASE64, Engine as _};
|
|
||||||
use colog;
|
use colog;
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
|
use serde::Deserialize;
|
||||||
use tokio::net::{TcpListener, TcpStream};
|
use tokio::net::TcpListener;
|
||||||
use tokio::sync::broadcast;
|
use tokio::sync::broadcast;
|
||||||
use x25519_dalek::{EphemeralSecret, PublicKey};
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
struct Config {
|
||||||
|
address: String,
|
||||||
|
port: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
// Initialize the logger
|
// Initialize the logger
|
||||||
colog::init();
|
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
|
// 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");
|
info!("Server running on port 8080");
|
||||||
|
|
||||||
// Create a broadcast channel for sharing messages
|
// 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