diff --git a/src/client.rs b/src/client.rs index 2cda450..27ad9ff 100644 --- a/src/client.rs +++ b/src/client.rs @@ -15,7 +15,7 @@ use crate::listener; pub async fn handle_client( client_socket: TcpStream, - proxy_info: listener::ProxyInfo, + mut proxy_info: listener::ProxyInfo, ) { println!("Accepting Connection"); let backend_addr = proxy_info.formatted_backend_address(); @@ -64,7 +64,7 @@ pub async fn handle_client( match server_conn { Some(mut server_conn) => { if login_handle::respond_login( - &proxy_info, + &mut proxy_info, &mut client_conn, &mut server_conn, ).await.expect( diff --git a/src/listener.rs b/src/listener.rs index bc3ddfc..9961878 100644 --- a/src/listener.rs +++ b/src/listener.rs @@ -4,6 +4,8 @@ use tokio::net::TcpListener; use std::error::Error; use rsa::RsaPrivateKey; +use crate::whitelist::Whitelist; + #[derive(Copy, Clone)] pub enum OnlineStatus { Online, @@ -25,6 +27,7 @@ pub struct ProxyInfo { pub private_key: RsaPrivateKey, pub online_status: OnlineStatus, pub authentication_method: AuthenticationMethod, + pub whitelist: Whitelist, } impl ProxyInfo { diff --git a/src/login_handle.rs b/src/login_handle.rs index faa8dd7..2de6ed1 100644 --- a/src/login_handle.rs +++ b/src/login_handle.rs @@ -1,11 +1,5 @@ // Yeahbut December 2023 -use std::fs; -use std::time::{Duration, Instant}; -use std::sync::{Arc, Mutex}; - -use serde_json::Value; -use lazy_static::lazy_static; use purple_cello_mc_protocol::{ mc_types::{self, Result, Packet, ProtocolConnection}, @@ -16,156 +10,10 @@ use purple_cello_mc_protocol::{ use purple_cello_mojang_api::multiplayer_auth; use crate::listener; - -const EXPIRATION_DURATION: Duration = Duration::from_secs(3600); - -struct CachedWhitelist { - whitelist_data: Value, - timestamp: Instant, -} - -#[derive(PartialEq)] -struct Player { - name: String, - player_uuid: Option, -} - -enum PlayerAllowed { - True(Player), - False(String), -} - -fn load_whitelist() -> Value { - let file_path = "./whitelist.json"; - - let data = match fs::read_to_string(file_path) { - Ok(data) => data, - Err(_) => return Value::Null, - }; - - let whitelist_data: Value = match serde_json::from_str(&data) { - Ok(value) => value, - Err(_) => return Value::Null, - }; - - whitelist_data -} - -fn get_whitelist() -> Vec { - lazy_static! { - static ref WHITELIST_CACHE: Arc>> = - Arc::new(Mutex::new(None)); - } - - let mut cache = WHITELIST_CACHE.lock().unwrap(); - - if let Some(cached_whitelist) = cache.as_ref() { - if cached_whitelist.timestamp.elapsed() >= EXPIRATION_DURATION { - println!("Refreshing whitelist cache"); - *cache = Some(CachedWhitelist { - whitelist_data: load_whitelist(), - timestamp: Instant::now(), - }); - } - } else { - *cache = Some(CachedWhitelist { - whitelist_data: load_whitelist(), - timestamp: Instant::now(), - }); - } - - let whitelist_data = cache.as_ref().unwrap().whitelist_data.clone(); - - std::mem::drop(cache); - - if whitelist_data == Value::Null { - return Vec::new(); - } - - let whitelist_array = match whitelist_data.as_array() { - Some(whitelist) => whitelist, - None => { return Vec::new(); } - }; - - let mut whitelist: Vec = Vec::new(); - - for whitelisted_player in whitelist_array { - let player_map = match whitelisted_player.as_object() { - Some(whitelist) => whitelist, - None => { continue; } - }; - - let name = match player_map.get("name") { - Some(name) => { - match name.as_str() { - Some(name) => name, - None => { continue; } - } - }, - None => { continue; } - }; - - let player_uuid = match player_map.get("uuid") { - Some(uuid) => { - match uuid.as_str() { - Some(uuid) => { - match u128::from_str_radix(uuid, 16) { - Ok(uuid) => uuid, - Err(_) => { continue; } - } - }, - None => { continue; } - } - }, - None => { continue; } - }; - - whitelist.push(Player { - name: name.to_string(), - player_uuid: Some(player_uuid), - }) - } - - whitelist -} - -fn check_player_whitelist(player: Player) -> PlayerAllowed { - - if player.player_uuid.is_none() { - return PlayerAllowed::False("Invalid UUID".to_string()); - } - - let whitelist = get_whitelist(); - - let mut invalid_uuid = false; - let mut invalid_username = false; - - for wl_player in whitelist { - if wl_player == player { - return PlayerAllowed::True(player); - } else if wl_player.name == player.name { - invalid_uuid = true; - } else if wl_player.player_uuid == player.player_uuid { - invalid_username = true; - } - } - - if invalid_uuid { - PlayerAllowed::False("Invalid UUID".to_string()) - } else if invalid_username { - PlayerAllowed::False( - "Invalid Username!\nPlease contact the admins to update your \ -username:\npurplecelloserver@gmail.com".to_string() - ) - } else { - PlayerAllowed::False("Not whitelisted on this server.\n\ -Please direct whitelist requests to the admins:\n\ -purplecelloserver@gmail.com".to_string()) - } -} +use crate::whitelist::{Player, PlayerAllowed}; async fn check_player( - proxy_info: &listener::ProxyInfo, + proxy_info: &mut listener::ProxyInfo, player: Player, client_conn: &mut ProtocolConnection<'_>, ) -> Result { @@ -183,22 +31,24 @@ async fn check_player( listener::AuthenticationMethod::Mojang => { match multiplayer_auth::joined( &player.name, &server_id, None).await { - Ok(_) => Ok(check_player_whitelist(player)), + Ok(_) => Ok(proxy_info.whitelist + .check_player_whitelist(player) + ), Err(_) => Ok(PlayerAllowed::False( "Mojang Authentication Failed".to_string() )), }}, listener::AuthenticationMethod::None => - Ok(check_player_whitelist(player)) + Ok(proxy_info.whitelist.check_player_whitelist(player)) } }, listener::OnlineStatus::Offline => - Ok(check_player_whitelist(player)), + Ok(proxy_info.whitelist.check_player_whitelist(player)), } } pub async fn respond_login( - proxy_info: &listener::ProxyInfo, + proxy_info: &mut listener::ProxyInfo, client_conn: &mut ProtocolConnection<'_>, server_conn: &mut ProtocolConnection<'_>, ) -> Result { @@ -225,7 +75,7 @@ pub async fn respond_login( } async fn login_to_proxy( - proxy_info: &listener::ProxyInfo, + proxy_info: &mut listener::ProxyInfo, client_conn: &mut ProtocolConnection<'_>, ) -> Result { println!("Logging into proxy"); @@ -236,6 +86,7 @@ async fn login_to_proxy( let player: Player = Player { name: start_packet.name, player_uuid: start_packet.player_uuid, + active: true, }; check_player(proxy_info, player, client_conn).await diff --git a/src/main.rs b/src/main.rs index 85bcb9c..709bb14 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,7 @@ mod status_handle; mod login_handle; mod client; mod listener; +mod whitelist; #[tokio::main] async fn main() -> Result<(), Box> { @@ -20,6 +21,8 @@ async fn main() -> Result<(), Box> { private_key: private_key.clone(), online_status: listener::OnlineStatus::Offline, authentication_method: listener::AuthenticationMethod::None, + whitelist: whitelist::Whitelist::WhitelistOpen( + whitelist::WhitelistOpen{}), }; let online_info = listener::ProxyInfo{ proxy_addr: "127.0.0.1".to_string(), @@ -29,6 +32,8 @@ async fn main() -> Result<(), Box> { private_key: private_key.clone(), online_status: listener::OnlineStatus::Online, authentication_method: listener::AuthenticationMethod::Mojang, + whitelist: whitelist::Whitelist::WhitelistFile( + whitelist::WhitelistFile::new("./whitelist.json".to_string())), }; let listener_offline: listener::TcpListenerWrapper = diff --git a/src/whitelist.rs b/src/whitelist.rs new file mode 100644 index 0000000..4c518de --- /dev/null +++ b/src/whitelist.rs @@ -0,0 +1,194 @@ +// Yeahbut June 2024 + +use std::fs; +use std::time::{Duration, Instant}; + +use serde_json::Value; + +const EXPIRATION_DURATION: Duration = Duration::from_secs(10); +// const EXPIRATION_DURATION: Duration = Duration::from_secs(60); + +#[derive(PartialEq)] +pub struct Player { + pub name: String, + pub player_uuid: Option, + pub active: bool, +} + +pub enum PlayerAllowed { + True(Player), + False(String), +} + +#[derive(Clone)] +pub enum Whitelist { + WhitelistOpen(WhitelistOpen), + WhitelistFile(WhitelistFile), +} + +impl Whitelist { + pub fn check_player_whitelist(&mut self ,player: Player) -> PlayerAllowed { + match self { + Whitelist::WhitelistOpen(wl) => wl.check_player_whitelist(player), + Whitelist::WhitelistFile(wl) => wl.check_player_whitelist(player), + } + } +} + +#[derive(Clone)] +pub struct WhitelistOpen {} + +impl WhitelistOpen { + pub fn check_player_whitelist(&mut self ,player: Player) -> PlayerAllowed { + PlayerAllowed::True(player) + } +} + +#[derive(Clone)] +pub struct WhitelistFile { + file_path: String, + whitelist_data: Value, + timestamp: Instant, +} + +impl WhitelistFile { + pub fn new(file_path: String) -> Self { + Self { + file_path, + whitelist_data: Value::Null, + timestamp: Instant::now() - EXPIRATION_DURATION, + } + } + + fn load(&mut self) { + let data = match fs::read_to_string(&self.file_path) { + Ok(data) => data, + Err(_) => "".to_string(), + }; + + self.whitelist_data = match serde_json::from_str(&data) { + Ok(value) => value, + Err(_) => Value::Null, + }; + + self.timestamp = Instant::now(); + } + + fn get_whitelist(&mut self) -> Vec { + if self.timestamp.elapsed() >= EXPIRATION_DURATION { + println!("Refreshing whitelist cache"); + self.load(); + } + + if self.whitelist_data == Value::Null { + return Vec::new(); + } + + let whitelist_array = match self.whitelist_data.as_array() { + Some(whitelist) => whitelist, + None => { return Vec::new(); } + }; + + let mut whitelist: Vec = Vec::new(); + + for whitelisted_player in whitelist_array { + let player_map = match whitelisted_player.as_object() { + Some(whitelist) => whitelist, + None => { continue; } + }; + + let name = match player_map.get("name") { + Some(name) => { + match name.as_str() { + Some(name) => name, + None => { continue; } + } + }, + None => { continue; } + }; + + let player_uuid = match player_map.get("uuid") { + Some(uuid) => { + match uuid.as_str() { + Some(uuid) => { + match u128::from_str_radix(uuid, 16) { + Ok(uuid) => uuid, + Err(_) => { continue; } + } + }, + None => { continue; } + } + }, + None => { continue; } + }; + + let active = match player_map.get("active") { + Some(active) => { + match active.as_bool() { + Some(active) => active, + None => { false } + } + }, + None => { false } + }; + + whitelist.push(Player { + name: name.to_string(), + player_uuid: Some(player_uuid), + active: active, + }); + } + + whitelist + } + + pub fn check_player_whitelist(&mut self ,player: Player) -> PlayerAllowed { + + if player.player_uuid.is_none() { + return PlayerAllowed::False( + "Invalid UUID (UUID Missing)".to_string()); + } + + let whitelist = self.get_whitelist(); + + let mut invalid_uuid = false; + let mut invalid_username = false; + let mut is_inactive = false; + + for wl_player in whitelist { + if wl_player.name == player.name && + wl_player.player_uuid == player.player_uuid { + if wl_player.active { + return PlayerAllowed::True(player); + } else { + is_inactive = true; + } + } else if wl_player.name == player.name && + wl_player.player_uuid != player.player_uuid { + invalid_uuid = true; + } else if wl_player.player_uuid == player.player_uuid && + wl_player.name != player.name { + invalid_username = true; + } + } + + if is_inactive { + PlayerAllowed::False("Whitelist Status Inactive!\n\ +Please contact the admins to reactivate:\n\ +purplecelloserver@gmail.com".to_string() + ) + } else if invalid_username { + PlayerAllowed::False("Invalid Username!\n\ +Please contact the admins to update your username:\n\ +purplecelloserver@gmail.com".to_string() + ) + } else if invalid_uuid { + PlayerAllowed::False("Invalid UUID".to_string()) + } else { + PlayerAllowed::False("Not whitelisted on this server.\n\ +Please direct whitelist requests to the admins:\n\ +purplecelloserver@gmail.com".to_string()) + } + } + +}